Testes Avançados em Go

Testes Avançados em Go

Como testar e escrever código testavel

308dfe1398173b9576bc23829fb9b92e?s=128

Felipe Oliveira

November 11, 2017
Tweet

Transcript

  1. 13/11/17 12'25 Testes Avançados em Go Page 1 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Testes Avançados em Go 11 November 2017 Felipe Oliveira Senior Software Engineer, Nuveo
  2. 13/11/17 12'25 Testes Avançados em Go Page 2 of 37

    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
  3. 13/11/17 12'25 Testes Avançados em Go Page 3 of 37

    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
  4. 13/11/17 12'25 Testes Avançados em Go Page 4 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Projetos Nuveo Software pREST (http://postgres.rest) gofn (https://github.com/nuveo/gofn)
  5. 13/11/17 12'25 Testes Avançados em Go Page 5 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 As duas partes de testar Metodologia de testes Escrever código testável
  6. 13/11/17 12'25 Testes Avançados em Go Page 6 of 37

    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)`
  7. 13/11/17 12'25 Testes Avançados em Go Page 7 of 37

    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
  8. 13/11/17 12'25 Testes Avançados em Go Page 8 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Table Driven Tests
  9. 13/11/17 12'25 Testes Avançados em Go Page 9 of 37

    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) } } }
  10. 13/11/17 12'25 Testes Avançados em Go Page 10 of 37

    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
  11. 13/11/17 12'25 Testes Avançados em Go Page 11 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Subtests
  12. 13/11/17 12'25 Testes Avançados em Go Page 12 of 37

    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) } }) } }
  13. 13/11/17 12'25 Testes Avançados em Go Page 13 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Diretório testdata
  14. 13/11/17 12'25 Testes Avançados em Go Page 14 of 37

    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
  15. 13/11/17 12'25 Testes Avançados em Go Page 15 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Dados de teste com flags
  16. 13/11/17 12'25 Testes Avançados em Go Page 16 of 37

    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! } }) } }
  17. 13/11/17 12'25 Testes Avançados em Go Page 17 of 37

    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()
  18. 13/11/17 12'25 Testes Avançados em Go Page 18 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Estado global
  19. 13/11/17 12'25 Testes Avançados em Go Page 19 of 37

    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
  20. 13/11/17 12'25 Testes Avançados em Go Page 20 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Helpers
  21. 13/11/17 12'25 Testes Avançados em Go Page 21 of 37

    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
  22. 13/11/17 12'25 Testes Avançados em Go Page 22 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Pacote e funções
  23. 13/11/17 12'25 Testes Avançados em Go Page 23 of 37

    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
  24. 13/11/17 12'25 Testes Avançados em Go Page 24 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Configuração
  25. 13/11/17 12'25 Testes Avançados em Go Page 25 of 37

    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
  26. 13/11/17 12'25 Testes Avançados em Go Page 26 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Subprocesso
  27. 13/11/17 12'25 Testes Avançados em Go Page 27 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Subprocesso func helperProcess(s ...string) *exec.Cmd { cs := []string{"-test.run=TestHelperProcess", "--"} cs = append(cs, s...) env := []string{ "GO_WANT_HELPER_PROCESS=1", } cmd := exec.Command(os.Args[0], cs...) cmd.Env = append(env, os.Environ()...) return cmd }
  28. 13/11/17 12'25 Testes Avançados em Go Page 28 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Subprocesso func TestHelperProcess(*testing.T) { if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { return } defer os.Exit(0) args := os.Args for len(args) > 0 { if args[0] == "--" { args = args[1:] break } args = args[1:] } cmd, args := args[0], args[1:] switch cmd { case “foo”: // .... } }
  29. 13/11/17 12'25 Testes Avançados em Go Page 29 of 37

    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
  30. 13/11/17 12'25 Testes Avançados em Go Page 30 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Interfaces
  31. 13/11/17 12'25 Testes Avançados em Go Page 31 of 37

    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
  32. 13/11/17 12'25 Testes Avançados em Go Page 32 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Testes como API pública
  33. 13/11/17 12'25 Testes Avançados em Go Page 33 of 37

    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
  34. 13/11/17 12'25 Testes Avançados em Go Page 34 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 TDD
  35. 13/11/17 12'25 Testes Avançados em Go Page 35 of 37

    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
  36. 13/11/17 12'25 Testes Avançados em Go Page 36 of 37

    http://localhost:3999/avanced_testing/testing.slide#1 Thank you Felipe Oliveira Senior Software Engineer, Nuveo felipeweb.programador@gmail.com (mailto:felipeweb.programador@gmail.com) https://www.felipeweb.net.br (https://www.felipeweb.net.br) @_felipeweb (http://twitter.com/_felipeweb)
  37. 13/11/17 12'25 Testes Avançados em Go Page 37 of 37

    http://localhost:3999/avanced_testing/testing.slide#1