http://localhost:3999/avanced_testing/testing.slide#1 Sobre mim Felipe Oliveira @_felipeweb (https://www.felipeweb.net.br) Mais de 3 anos de expriência com Go
http://localhost:3999/avanced_testing/testing.slide#1 Nuveo Logo Nuveo (https://www.nuveo.ai) Mais ou menos 90% da base de Código em Go Clientes como Bradesco, Cyrela, Vivo e outros grandes
http://localhost:3999/avanced_testing/testing.slide#1 Metodologia Como testar cenários especificos? Como escrever testes eficientes? Um teste é muito mais que um `assert(func() == expected)`
http://localhost:3999/avanced_testing/testing.slide#1 Código testável Como escrever um código para ser fácilmente bem testado? Tão importante quanto escrever bons testes é escrever um código que possa ser testado Refatorar código existente para que ele fique testável é doloroso, mas necessário TDD
http://localhost:3999/avanced_testing/testing.slide#1 Table Driven Tests func TestAdd(t *testing.T) { testCases := []struct { a int b int expected int }{ {1, 1, 2}, {1, -1, 0}, } for _, tc := range testCases { r := Add(tc.a, tc.b) if r != tc.expected { t.Errorf("expected %v, but got %v", tc.expected, r) } } }
http://localhost:3999/avanced_testing/testing.slide#1 Table Driven Tests Muito fácil adicionar um novo caso de teste Torna fácil testar cenários complexos Torna fácil reproduzir um bug para o cenário testado Use esse pattern mesmo se o cenário só tiver um caso de teste
http://localhost:3999/avanced_testing/testing.slide#1 Subtests func TestAdd(t *testing.T) { testCases := []struct { name string a int b int expected int }{ {"positive number sum", 1, 1, 2}, {"negative number sum", -1, -1, -2}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T){ r := Add(tc.a, tc.b) if r != tc.expected { t.Errorf("expected %v, but got %v", tc.expected, r) } }) } }
http://localhost:3999/avanced_testing/testing.slide#1 Diretório testdata func TestAdd(t *testing.T) { // omited code data := filepath.Join("testdata", tc.Name) // do something } O "go test" configura o diretório do teste como diretório de trabalho atual Use o diretório para gravar os dados de teste Muito útil para arquivos de configuração
http://localhost:3999/avanced_testing/testing.slide#1 Dados de teste com flags var update = flag.Bool(“update”, false, “update golden files”) func TestSomething(t *testing.T) { // omited code for _, tc := range testCases { t.Run(tc.name, func(t *testing.T){ actual := doSomething(tc) golden := filepath.Join(“testdata”, fmt.Sprintf("%s.%s",tc.Name, tc.FileType)) if *update { ioutil.WriteFile(golden, actual, 0644) } expected, _ := ioutil.ReadFile(golden) if !bytes.Equal(actual, expected) { // FAIL! } }) } }
http://localhost:3999/avanced_testing/testing.slide#1 Dados de teste com flags go test ... go test -update Fácil de testar output complexos Fácil de comparar diferenças entre arquivos Auxilia a escrever a função String()
http://localhost:3999/avanced_testing/testing.slide#1 Estado global // Not good on its own const contextDir = "./" // Better var contextDir = "./" // Best const defaultContextDir = "./" type BuildOptions struct { ContextDir string } Evite sempre que possivel Em último caso quando usar estado global use var permitindo os testes modificar o estado
http://localhost:3999/avanced_testing/testing.slide#1 Helpers func testTempFile(t *testing.T) (string, func()) { t.Helper() // go 1.9 or higher tf, err := ioutil.TempFile(“”, “test”) if err != nil { t.Fatalf(“err: %s”, err) } tf.Close() return tf.Name(), func() { os.Remove(tf.Name()) } } func TestThing(t *testing.T) { tf, tfclose := testTempFile(t) defer tfclose() } Nunca retorne erro passe *testing.T como parametro e falhe o teste Use helpers para deixar o teste mais claro
http://localhost:3999/avanced_testing/testing.slide#1 Pacote e funções Quebre a compibilidade da sua API, mas seja criterioso com isso Não exagere, faça isso onde faz sentido Fazer isso corretamente ajudará a testar e deixa o código mais organizado Fazer isso em excesso deixa o testes complicados Com prática você encontrará o ponto de parada Teste somente sua API pública
http://localhost:3999/avanced_testing/testing.slide#1 Configuração type Storing struct { session *session.Session } Use variáveis de ambiente Coloque fields não exportado nas structs para facilitar a configuração do teste
http://localhost:3999/avanced_testing/testing.slide#1 Subprocesso Executa um subprocesso com implementação mock do comando A stdlib faz assim no pacote os/exec
http://localhost:3999/avanced_testing/testing.slide#1 Interfaces Interfaces são pontos de mock Use com cautela, o uso em exceso de interfaces difculta a legibilidade do código
http://localhost:3999/avanced_testing/testing.slide#1 Testes como API pública // FakeServer SSH type FakeServer struct { t *testing.T conf *ssh.ServerConfig listener net.Listener Cmd string Reply string ConnDelay time.Duration ExecDelay time.Duration ExitStatus int } Exponha seus mocks como API pública Evite que outos pacotes reinventem a roda pra testar código que dependam de seus componentes Como toda API pública, também precisa de testes
http://localhost:3999/avanced_testing/testing.slide#1 TDD Ajuda ao programador pensar na API pública de forma que ela fique testavel Precisa de prática para conseguir fazer de forma natural