/vvt

Code and project used by Validation,Verification and Tests course

Primary LanguageGo

Validação, Verificação e Testes

Este projeto tem como objetivo servir de suporte ao curso de Verificação, Validação e Testes ministrado pelo professor Daniel Fireman.

Se você procura dados sobre uma turma específica:

Está página conterá todo o código fonte bem como um passo-a-passo da parte prática do curso. Uma vez que será completamente efeutado pelo professor e o foco principal são os conceitos ministrados, o passo-a-passo poderia ser ministrado em qualquer linguagem de programação. Escolhemos Go para esse curso. Para uma lista mais completa de motivos para a escolha de Go, por favor clique aqui.

A aplicação a ser criada é um serviço de gerenciamento de TODOs. Para fins ilustrativos a aplicação armazena a lista de TODOs em memória. A API /todos tem 3 métodos: PUT (adicionar item) e GET (listar itens).

Heroku Coverage Status

Configuração do ambiente

Pessoas curiosas podem aprender o básico de Go aqui. Web app developers podem começar aqui.

Instalação da runtime Go

Eu achei mais fácil usar gvm para instalação e manutenção de runtimes Go. Intruções mais detalhadas aqui. É necessário ter o comando curl instalado.

$ bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
$ gvm install go1.5
$ source ~/.bashrc  # ou fechar e abrir o terminal
$ gvm use go1.5 [--default]
$ echo "export GOPATH={SEU GOPATH}" >> ~/.bashrc
$ echo "export PATH=$PATH:$GOPATH/bin" >> ~/.bashrc

Dependências e código do servidor de TODOs

Para simplificar o código do servidor REST fazemos uso da biblioteca labstack/echo, outras bibliotecas também poderiam ser usadas, por exmplo Gorilla Mux, Gin e Negroni. Esse passo só precisara ser executado uma vez.

$ go get -u github.com/labstack/echo  # labstack/echo
$ go get -u github.com/stretchr/testify/assert # assert
$ go get github.com/danielfireman/vvt

Após execução, o código fonte do servidor de TODOs poderá ser encontrado em :

$GOPATH/src/github.com/danielfireman/vvt

Executar servidor e utilizar o serviço

O comando abaixo compilará o código do servidor e executará o programa que aceitará requisções na porta 8999.

$ go run $GOPATH/src/github.com/danielfireman/vvt/cmd/server/main.go --port=8999
Server listening at localhost:8999

Pronto! A essa altura do campeonato temos o servidor executando e a API de gerênciamento de TODOs pronta para receber requisições na porta 8999. Vamos fazer alguns testes para entender melhor as funcionalidades do serviço. Utilizaremos curl para enviar as requisições.

 $ curl -H "Content-Type: application/json" -X POST -d '{"desc":"Comprar legumes"}' localhost:8999/todo
 {"Desc":"Comprar legumes"}
 $ curl -H "Content-Type: application/json" -X POST -d '{"desc":"Comprar verduras"}' localhost:8999/todo
 {"Desc":"Comprar verduras"}

Os comandos enviam requisições POST para http://localhost:8999/todo. A requisição é do tipo JSON e contém {"desc":"Comprar legumes"} e {"desc":"Comprar verduras"}. Ao final das execuções a lista de coisas a fazer terá dois items, como pode ser verificado através do comando abaixo:

 $ curl -H "Content-Type: application/json" -X GET localhost:8999/todo
 ["Comprar legumes","Comprar verduras"]

Para remover o primeiro item da lista podemos usar o comando DELETE.

 $ curl -H "Content-Type: application/json" -X DELETE localhost:8999/todo/0
 $ curl -H "Content-Type: application/json" -X GET localhost:8999/todo
 ["Comprar verduras"]

Executando os testes

$ cd $GOPATH/src/github.com/danielfireman/vvt/todo
$ go test -v

Adicionando o método DELETE

Iremos utilizar a metodologia de desenvolvimento orientado a testes (TDD) para adicionar a funcionalidade de remoção de elementos da lista de TODOs. A API terá como parâmetro o índice do elemento na lista.

Como estamos seguindo TDD, a primeira coisa a fazer é adicionar o teste.

func TestDelete(t *testing.T) {
}

Ao executar os testes temos:

$ cd $GOPATH/src/github.com/danielfireman/vvt/todo
$  go test -v -coverprofile=/tmp/c.out && go tool cover -html=/tmp/c.out
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN   TestList
--- PASS: TestList (0.00s)
=== RUN   TestDelete
--- PASS: TestDelete (0.00s)
PASS
coverage: 100.0% of statements
ok  	github.com/danielfireman/vvt/todo	0.003s

Todos os testes passando e temos 100% de cobertura! Agora vamos melhorar um pouco o teste e a uma primeira versão da implementação.

// todo_test.go
func TestDelete(t *testing.T) {
	s := store{[]string{"foo", "bar"}}
	assert.Nil(t, s.Delete(0))
	assert.Equal(t, s.content[0] != "bar", "bar should be the first element")
}

// todo.go
func (s *store) Delete(n int) error {
	return nil
}

Ao re-executar os testes temos a primeira etapa do TDD, uma falha.

$ cd $GOPATH/src/github.com/danielfireman/vvt/todo
$  go test -coverprofile=/tmp/c.out && go tool cover -html=/tmp/c.out
--- FAIL: TestDelete (0.00s)
...

E a implementação:

func (s *store) Delete(n int) error {
 	s.content = append(s.content[:n], s.content[n+1:]...)
	return nil
}

E temos os testes passando e cobertura de 100%. Tudo verde, não? Vocês tem algum problema? O que aconteceria se o índice passado para deleção não estivesse nos limites da lista? Isso nos leva a mais uma iteração do TDD, mais uma falha. Ao adicionar o trecho abaixo ao test e re-executar os testes teremos mais uma falha.

	assert.NotNil(t, s.Delete(1))

Por fim, vamos corrigir essa falha na implementação finalizamos a adição da API ao serviço com os seguinte código.

// todo_test.go
func TestDelete(t *testing.T) {
	s := store{[]string{"foo", "bar"}}
	assert.Nil(t, s.Delete(0))
	assert.Equal(t, s.content[0] != "bar", "bar should be the first element")
	assert.NotNil(t, s.Delete(1), "there is no index 1, must error")
}

// todo.go
func (s *store) Delete(n int) error {
	if n < 0 || n >= len(s.content) {
		return errors.New("Invalid position.")
	}
	s.content = append(s.content[:n], s.content[n+1:]...)
	return nil
}

// server.go
	e.Delete("/todo/:id", func(c *echo.Context) error {
		param := c.Param("id")
		id, err := strconv.Atoi(param)
		if err != nil {
			msg := fmt.Sprintf("Invalid parameter: %v", param)
			return c.JSON(http.StatusPreconditionFailed, msg)
		}
		if err := s.Delete(id); err != nil {
			return c.JSON(http.StatusNotFound, err.Error())
		}
		return c.NoContent(http.StatusNoContent)
	})

Pronto! Ao subir o servidor, o serviço já estará pronto para receber requisições.

curl -H "Content-Type: application/json" -X DELETE localhost:8999/todo/0

Apendice

Porque Go

Uma vez que será completamente efeutado pelo professor e o foco principal são os conceitos ministrados, o passo-a-passo poderia ser feito em qualquer linguagem de programação. Dentre os motivos que nos levaram a escolher Go podemos listar:

  • Sintaxe simples e enxuta, nos permitindo focar nos conceitos
  • Suporte a testes de unidade embutido na linguagem: pacote testing
    • Inclui benchmarks e perfis de processamento e memória
  • Suporte primário a testes de integração embutidos na linguagem: pacote httptest
  • Muitas ferramentas inspeção de código com simples instlação e utilização, por exemplo:
    • Construções suspeitas: Vet
    • Errors de estilo: GoLint
    • Erros não verificados: ErrCheck
    • Injeção SQL: SafeSQL
    • Expressões defer repetidas, structs com campos não-utilizados ou estruturada ineficientemente, variavéis e constantes globaus não-utilizadas: OpennotaCheck
  • Debugger de simples instalação e utilização: delve
  • Suportada por drone.io, ferramenta de entrega contínua
  • Suportada por Heroku, plataforma onde será feito o deployment da aplicação