> For the complete documentation index, see [llms.txt](https://womenwhogocwb.gitbook.io/letsgo/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://womenwhogocwb.gitbook.io/letsgo/mais/testes.md).

# Testes

## Introdução

Escrever testes para um código é parte essencial do desenvolvimento de aplicações robustas. É extremamente importante sabermos testar a sua validade, conseguir fazer alterações em código existente e saber se elas afetarão demais partes do sistema.

Existem diferentes tipos de testes. Aqui, focaremos em testes unitários.

{% hint style="info" %}
**Motivações para escrever testes unitários**

Para ir mais longe no porquê escrever testes unitários, acesse [esse conteúdo sugerido](https://larien.gitbook.io/aprenda-go-com-testes/meta/motivacao).[ ](https://larien.gitbook.io/aprenda-go-com-testes/meta/motivacao)
{% endhint %}

Em Go, tudo que é necessário para testar já está disponível na biblioteca padrão. O pacote que provê o essencial se chama [`testing`](https://pkg.go.dev/testing).

Para executar os testes presentes na aplicação, usamos o comando `go test`.

## Desenvolvimento orientado a testes

Vamos aproveitar e introduzir a prática do TDD (do inglês *Test Driven Development*). Vamos abordar apenas o suficiente para explicar testes em Go, mas existem diversos sites, vídeos e livros publicados sobre TDD que você pode buscar posteriormente caso queira saber mais sobre.&#x20;

A ideia do TDD é que trabalhemos em ciclos:

1. Primeiro escrevemos um teste que inicialmente irá falhar, tendo em vista que o código ainda não foi escrito
2. Implementamos a funcionalidade com o mínimo de código para o teste passar
3. Refatoramos para eliminar redundância e melhorar a legibilidade

Como exemplo, nossa tarefa será escrever uma calculadora (ou melhor, calculadora que faça apenas somas 😆).

## Desenvolvendo uma calculadora\* usando TDD

Como primeiro passo, começamos a planejar o que o primeiro teste precisa:

* Precisamos de uma função que faça a soma de dois algarismos;
* Quando chamarmos esta função, queremos “guardar” o seu retorno em uma variável;
* Queremos então comparar o valor recebido da função com o valor que esperávamos.

### Passo 1: Escrevendo o teste

Vamos ao teste: criaremos o arquivo `calculadora_test.go` e representaremos no código o que foi descrito acima

```go
package calculadora

import "testing"

func TestAdicione(t *testing.T) {
  obtive := Adicione(3, 4)
  espero := 7
  if obtive != espero {
     t.Errorf("espero '%d', mas obtive '%d'", espero, obtive)
  }
}
```

**O que aconteceu até aqui? Vamos explicar…**

Primeiro, criamos um arquivo chamado `calculafora_test.go`:&#x20;

* Nomeamos o arquivo terminando com \_test.go. Assim ele será ignorado quando o executável da aplicação for criado, mas será incluído quando o comando go test for chamado.

Em seguida, criamos a função que é o teste em si:

* O nome da função segue o padrão `TestXxx`: Xxx serve para identificar o nome da função que está sendo testada e deve iniciar com letra maiúscula.
* Como parâmetro, teremos a variável `t` que é um ponteiro para `testing.T`, que por sua vez é um tipo fornecido pela biblioteca de testes do Go, que serve para gerenciar o estado do teste e fornecer suporte à formatação de mensagens.

Depois disso, começamos a implementar o teste propriamente dito:

* Caso o valor não seja o esperado, utilizamos um dos métodos que `testing.T` fornece para formatar e imprimir uma mensagem de erro em testes - como `t.Errorf()`, que usamos nesse exemplo.

Pronto, agora vamos rodar o teste usando o seguinte comando:

```
go test
```

E a seguinte mensagem será mostrada:

```
./calculadora_test.go:6:12: undefined: Adicione
```

Ótimo! Era isso mesmo que esperávamos: a função `Adicione()` ainda não foi implementada, por isso a mensagem de erro indica que Adicione é "undefined".

Seguimos em frente e a adicionamos a um arquivo chamado `calculadora.go`, que criaremos no mesmo pacote que o teste está e adicionamos o mínimo de código a fim de compilar.

```go
package calculadora

func Adicione(x, y int) int {
  return 0
}
```

Agora, ao rodarmos o comando `go test` novamente, o erro é outro. Nos livramos do erro de compilação, mas agora recebemos um erro relacionado ao teste:

```
calculadora_test.go:9: espero '7', mas obtive '0'
```

### Passo 2: Fazendo o teste passar

Agora sim, vamos para o próximo passo, que é fazer o teste passar. E qual é o passo mais rápido para fazê-lo executar com sucesso?

Pode causar estranhamento, mas, nessa etapa, o passo que buscamos é simplesmente substituir o zero pelo resultado esperado no único caso de testes que temos, dessa forma:

```go
package calculadora

func Adicione(x, y int) int {
  return 7
}
```

O que Kent Beck, criador do método, fala sobre essa etapa é que devemos cometer quaisquer “pecados” (sim, o autor fala em *sins* no original em inglês) que nos façam obter o resultado esperado. Explorar os diferentes casos de teste e iterar o ciclo básico do TDD (escrever o teste, fazer o teste passar e refatorar para eliminar redundâncias) nos levará a superar quaisquer aberrações que adicionarmos nessa etapa.

Agora, quando rodarmos o teste novamente com o comando `go test`, ele passará (aqui usamos a *flag* `-v`, para exibir o resultado de forma verbosa, então o comando ficou assim: `go test -v`).

```
=== RUN   TestAdicione
--- PASS: TestAdicione (0.00s)
```

### Passo 3: Refatorando para eliminar redundâncias

Nessa etapa, a ideia é eliminar todo tipo de duplicação que tenha sido inserida para os testes passarem. No nosso caso, onde está essa duplicação?

O número 7 aparece tanto no nosso teste (na linha 7 do arquivo `calculadora_test.go`: `espero := 7` ) e no retorno da função sendo testada (na linha 4 do arquivo `calculadora.go`: `return 7`).

Como removemos essa duplicação? Bem, **o caso esperado do teste precisa permanecer**, afinal é ele que nos diz o que esperamos obter da função que está sendo testada. Assim, o que podemos mudar é o retorno da função: não mais fazê-la retornar o número 7 fixo, mas sim a soma de seus dois parâmetros.

```go
package calculadora

func Adicione(x, y int) int {
  return x + y
}
```

Quando rodamos os testes, eles permanecem passando:

```
=== RUN   TestAdicione
--- PASS: TestAdicione (0.00s)
```

Com essa mudança, nosso código passa no caso de teste e está livre de redundâncias. Note que do passo 2 para o passo 3 os testes não devem quebrar: eles precisam continuar passando. Devemos remover as redundâncias e “pecados” do nosso código, mas não alterar a saída da função testada.

Em teoria, temos o código pronto e funcionando para outras somas que fizermos. Vamos testar se isso é verdade? Faremos isso adicionando um novo caso de testes ao nosso arquivo `calculadora_test.go`:

```go
package calculadora

import "testing"

func TestAdicione(t *testing.T) {
  t.Run("a soma de 3 e 4 é igual a 7", func(t *testing.T) {
     obtive := Adicione(3, 4)
     espero := 7
     if obtive != espero {
        t.Errorf("espero '%d', mas obtive '%d'", espero, obtive)
     }
  })
  t.Run("a soma de 27 e 113 é igual a 140", func(t *testing.T) {
     obtive := Adicione(27, 113)
     espero := 140
     if obtive != espero {
        t.Errorf("espero '%d', mas obtive '%d'", espero, obtive)
     }
  })
}
```

Note que adicionamos o método `t.Run()` da biblioteca de testes (pacote `testing`): ele roda sub-testes dentro de uma mesma função de testes, cada um desses sub-testes terá o nome que for passado como primeiro argumento. No exemplo, usamos os nomes "a soma de 3 e 4 é igual a 7" e "a soma de 27 e 113 é igual a 140".

Como esperávamos, os testes continuam passando! Esse é o resultado que obtemos ao rodar `go test -v`:

```
=== RUN   TestAdicione
=== RUN   TestAdicione/a_soma_de_3_e_4_é_igual_a_7
=== RUN   TestAdicione/a_soma_de_27_e_113_é_igual_a_140
--- PASS: TestAdicione (0.00s)
    --- PASS: TestAdicione/a_soma_de_3_e_4_é_igual_a_7 (0.00s)
    --- PASS: TestAdicione/a_soma_de_27_e_113_é_igual_a_140 (0.00s)
PASS
```

Assim, podemos seguir adicionando casos até alcançarmos todos os cenários que julgarmos necessários.

## Testes orientados por tabela

Quando adicionamos o segundo caso de testes, pudemos notar bastante código sendo repetido no arquivo `calculadora_test.go`. Quando os casos de teste são muito similares e nos levam a escrever código repetido, podemos aplicar os testes orientados por tabela (traduzido do inglês *Table-driven tests*).

Como faremos? Vamos por partes:

Primeiro, utilizaremos a sintaxe de `struct` literal para representar os atributos que precisamos e escrever uma lista de testes para remover a duplicação de código dos próprios testes.

```go
testes := []struct {
  nome      string
  operando1 int
  operando2 int
  esperado  int
}{
  {
     nome:      "a soma de 3 e 4 é igual a 7",
     operando1: 3,
     operando2: 4,
     esperado:  7,
  },
  {
     nome:      "a soma de 27 e 113 é igual a 140",
     operando1: 27,
     operando2: 113,
     esperado:  140,
  },
}
```

Tudo que precisamos de entrada para os testes está representado nessa struct: o nome do sub-teste, os dois operandos da soma e o resultado esperado.

Em seguida, vamos adicionar uma instrução `for range` para percorrer todos os elementos da `struct` "testes" e, em cada iteração:

* Chamar o método que roda sub-testes, passando o nome de cada sub-teste
* Chamar a função `Adicione()`, passando como argumentos os operandos 1 e 2
* Fazer a comparação entre esperado e obtido

Dessa forma:

```go
for _, teste := range testes {
  t.Run(teste.nome, func(t *testing.T) {
     obtive := Adicione(teste.operando1, teste.operando2)
     if obtive != teste.esperado {
        t.Errorf("espero '%d', mas obtive '%d'", teste.esperado, obtive)
     }
  })
}
```

É importante aqui entendermos se tudo continua funcionando como o esperado. Desta forma, adicionamos todos os testes que nosso negócio planeja implementar com facilidade. Por exemplo, vamos adicionar mais dois casos de teste:

```go
package calculadora

import "testing"

func TestAdicione(t *testing.T) {

  testes := []struct {
     nome      string
     operando1 int
     operando2 int
     esperado  int
  }{
     {
        nome:      "a soma de 3 e 4 é igual a 7",
        operando1: 3,
        operando2: 4,
        esperado:  7,
     },
     {
        nome:      "a soma de 27 e 113 é igual a 140",
        operando1: 27,
        operando2: 113,
        esperado:  140,
     },
     {
        nome:      "a soma de 1 e -1 é igual a 0",
        operando1: 1,
        operando2: -1,
        esperado:  0,
     },
     {
        nome:      "a soma de 15 e 1059 é igual a 1074",
        operando1: 15,
        operando2: 1059,
        esperado:  1074,
     },
  }

  for _, teste := range testes {
     t.Run(teste.nome, func(t *testing.T) {
        obtive := Adicione(teste.operando1, teste.operando2)
        if obtive != teste.esperado {
           t.Errorf("espero '%d', mas obtive '%d'", teste.esperado, obtive)
        }
     })
  }
}
```

Se rodarmos o comando `go test -v`, receberemos o seguinte:

```
=== RUN   TestAdicione
=== RUN   TestAdicione/a_soma_de_3_e_4_é_igual_a_7
=== RUN   TestAdicione/a_soma_de_27_e_113_é_igual_a_140
=== RUN   TestAdicione/a_soma_de_1_e_-1_é_igual_a_0
=== RUN   TestAdicione/a_soma_de_15_e_1059_é_igual_a_1074
--- PASS: TestAdicione (0.00s)
    --- PASS: TestAdicione/a_soma_de_3_e_4_é_igual_a_7 (0.00s)
    --- PASS: TestAdicione/a_soma_de_27_e_113_é_igual_a_140 (0.00s)
    --- PASS: TestAdicione/a_soma_de_1_e_-1_é_igual_a_0 (0.00s)
    --- PASS: TestAdicione/a_soma_de_15_e_1059_é_igual_a_1074 (0.00s)
PASS
```

Percebeu que adicionar novos testes ficou muito simples?

Esse tipo de abordagem é bastante útil quando não existem (ou existem poucas) variações entre os casos de teste.

## Conteúdo adicional

#### Aprenda Go com Testes

Sugerimos esse conteúdo para aprofundar os conhecimentos em testes (e em Go também): [Aprenda Go com Testes](https://larien.gitbook.io/aprenda-go-com-testes/)

#### Documentação do pacote `testing` da biblioteca padrão:

[Acesse aqui](https://pkg.go.dev/testing)

#### Se inglês não for um problema, consulte aqui também:

[Blogpost "Prefer table-driven tests"](https://dave.cheney.net/2019/05/07/prefer-table-driven-tests)

[Slides golang talks: "Testing"](https://talks.golang.org/2014/testing.slide)
