Um workshop que eu e Dherik Barison fizemos para levantar alguns pontos sobre como escrever bons testes, também levantamos alguns argumentos sobre o que existe no mundo de sotfware quando o tema é Testes, um workshop para trazer discussões das pessoas que o assistiram, para que conseguíssemos articular os assuntos de uma forma aberta, trazendo muitas vezes mais questionamentos do que respostas para os participantes
=D
2. “Trying to improve software quality by increasing the
amount of testing is like trying to lose weight by
weighing yourself more often”
Steve McConnell - Code Complete
3. Motivação
• Importância
• Pouca conversa aprofundada sobre o tema
• Será que realmente sabemos o fazer um teste
ser bom...
• Falta de visibilidade de que o código de um
teste deve ser tão bom quanto um código de
“business”
4. O que faz um teste ser bom?
• Performance
• Manutenabilidade
• Clareza
• Resiliência
• Precisão
5. Como escrever um teste bom?
• Testes devem servir como uma documentação do sistema
• Os nomes dos testes devem dizer o que eles fazem
• Não deve conhecer detalhes de implementação*
• Não deve exibir informações que distraiam do objetivo do
teste
• Um novo comportamento na aplicação não deve alterar os
testes antigos, apenas adicionar novos testes
6. Como conseguir um teste bom?
Ruim:
@Test
public void shouldPerformAddition() {
Calculator calculator = new Calculator(new RoundingStrategy(),
"unused", ENABLE_COSIN_FEATURE, 0.01, calculusEngine, false);
int result = calculator.doComputation(makeTestComputation());
assertEquals(5, result); // De onde este número veio?
}
7. Como conseguir um teste bom?
Ruim:
@Test
public void shouldPerformAddition() {
Calculator calculator = new Calculator(new RoundingStrategy(),
"unused", ENABLE_COSIN_FEATURE, 0.01, calculusEngine, false);
int result = calculator.doComputation(makeTestComputation());
assertEquals(5, result); // De onde este número veio?
}
8. Como conseguir um teste bom?
Ruim:
@Test
public void shouldPerformAddition() {
Calculator calculator = new Calculator(new RoundingStrategy(),
"unused", ENABLE_COSIN_FEATURE, 0.01, calculusEngine, false);
int result = calculator.doComputation(makeTestComputation());
assertEquals(5, result); // De onde este número veio?
}
9. Como conseguir um teste bom?
Bom:
@Test
public void shouldPerformAddition() {
Calculator calculator = newCalculator();
int result = calculator.doComputation(makeAdditionComputation(2,3));
assertEquals(5, result);
}
10. Como conseguir um teste bom?
Bom:
@Test
public void shouldPerformAddition() {
Calculator calculator = newCalculator();
int result = calculator.doComputation(makeAdditionComputation(2, 3));
assertEquals(5, result);
}
11. Como conseguir um teste bom?
Bom:
@Test
public void shouldPerformAddition() {
Calculator calculator = newCalculator();
int result = calculator.doComputation(makeAdditionComputation(2, 3));
assertEquals(5, result);
}
12. Como conseguir um teste bom?
Ruim:
@Test
public void isUserLockedOut_invalidLogin() {
authenticator.authenticate(username, invalidPassword);
assertFalse(authenticator.isUserLockedOut(username));
authenticator.authenticate(username, invalidPassword);
assertFalse(authenticator.isUserLockedOut(username));
authenticator.authenticate(username, invalidPassword);
assertTrue(authenticator.isUserLockedOut(username));
}
13. Como conseguir um teste bom?
Bom:
@Test
public void isUserLockedOut_lockOutUserAfterThreeInvalidLoginAttempts() {
authenticator.authenticate(username, invalidPassword);
assertFalse(authenticator.isUserLockedOut(username));
authenticator.authenticate(username, invalidPassword);
assertFalse(authenticator.isUserLockedOut(username));
authenticator.authenticate(username, invalidPassword);
assertTrue(authenticator.isUserLockedOut(username));
}
14. Quais as dificuldades mais comuns?
• Preparação dos cenários
• Builder e Fluent
• Load-data
• Como identificar o que é bom ser testado
(tudo?). Como criar estes testes?
15. Preparação dos cenários
• É o local mais comum para ocorrer duplicação
de código
• Favorece o reuso desnecessário de cenários
• O que podemos fazer?
16. Preparação dos cenários
Usar patterns como o Builder e o Fluent ajudam,
pois:
• Elimina código desnecessário
• Evita duplicação
• Incentiva o reuso dos mesmos valores de
variáveis entre os testes
17. Sem Builder e sem Fluent
public class CompanyTest {
@Test
public void testInsertCompany(){
Company company = new Company("Mc Donalds", "41.304.875/0001-64");
//teste de inserir
}
@Test
public void testUpdateCompany(){
Company company = new Company("Company 1", "58.455.457/0001-70");
//teste de atualização
}
@Test public void testRemoveCompany(){
Company company = new Company("ACME", "68.132.955/0001-01");
//teste de remoção
}
}
18. Builder com Fluent
public class CompanyTest {
@Test public void testInsertCompany(){
Company company = CompanyBuilder().build();
//teste de inserir
}
@Test public void testUpdateCompany(){
Company company = CompanyBuilder().build();
//teste de atualização
}
@Test public void testRemoveCompany(){
Company company = CompanyBuilder().withName(“Dextra”).build();
//teste de remoção
}
}
19. Builder com Fluent
public class CompanyBuilder {
private String cnpj = "41.304.875/0001-64";
private String name = "Company Name";
public Company build() {
return new Company().withCnpj(cnpj).withName(name);
}
public CompanyBuilder withCnpj(String cnpj) {
this.cnpj = cnpj;
return this;
}
public CompanyBuilder withName(String name) {
this.name = name;
return this;
}
}
20. Load-data
• O que é?
• Load-data muito grande, com muitos dados, é
realmente necessário?
• Usar informações de um load-data para um
teste é bom?
• Se for utilizar um load-data, como fazer isso
de uma maneira fácil para entendimento e
construção de um teste?
21. Load-data
• Utilização de FIXTURES para facilitar a
criação de objetos, evitar consultas ao banco
e garantir a consistência dos dados na
utilização em testes.
22. Load-data
public enum PessoaFixture implements EnumFixture {
JamesTKirk("JAMES T. KIRK", "JAMES",TipoPessoa.BR,"1986-01-01");
public String toInsertString() ;
public Long getId();
public Pessoa toEntidade() {
return EnumFixtureUtil.preencherAtributos(new Pessoa(), this);
}
}
24. Preparação dos cenários
• Em testes funcionais ou de integração, é
comum alguns testes partirem da mesma base
de informações. Exemplo de uma aplicação
bancária: exige uma Pessoa, Pessoa Física,
Usuário, Conta Corrente…
Como lidar?
25. Preparação dos cenários
• Deixar claro os cenários básicos para reuso!
Exemplo:
public class PessoaTeste {
@Test
public void testeMovimentacaoContaCorrente {
criarCenarioBasicoPessoaComContaCorrente();
//codigo do teste
}
public void criarCenarioBasicoPessoaComContaCorrente() {
// codigo do cenario
}
}
26. Preparação dos cenários
Alguns alertas sobre preparação de cenários:
Usar com cautela o @BeforeTest e
@BeforeClass. São usados apenas quando
você tem absoluta certeza que TODOS os
testes precisam do código que pretende colocar
lá sob estas anotações.
27. O que testar?
• Mesmo com TDD, esta pergunta é válida;
• Teste comportamentos e não métodos;
28. Testar comportamento x método
• Depois de escrever um método, é fácil
escrever um teste que verifica o que ele faz.
Mas complica se achar que os testes e
métodos público devem ter relação 1:1
• Nós queremos realmente é testar o seu
comportamento!
29. Teste de método
@Test public void testProcessTransaction() {
User user = newUserWithBalance(LOW_BALANCE_THRESHOLD.plus(dollars(2));
transactionProcessor.processTransaction(user, new Transaction("Pile of
Beanie Babies", dollars(3)));
assertContains("You bought a Pile of Beanie Babies", ui.getText());
assertEquals(1, user.getEmails().size());
assertEquals("balance is low”, user.getEmails().get(0).getSubject());
}
30. Teste de comportamento
@Test public void testProcessTransaction_displaysNotification() {
transactionProcessor.processTransaction(
new User(), new Transaction("Pile of Beanie Babies"));
assertContains("You bought a Pile of Beanie Babies", ui.getText());
}
@Test public void testProcessTransaction_sendsEmailWhenBalanceIsLow() {
User user = newUserWithBalance(LOW_BALANCE_THRESHOLD.plus(dollars(2));
transactionProcessor.processTransaction(
user,
new Transaction(dollars(3)));
assertEquals(1, user.getEmails().size());
assertEquals("Your balance is low", user.getEmails().get(0).getSubject());
}
31. Você testa o comportamento de suas exceptions?
@Test(expectedExceptions = ValidacaoException.class)
public void validarQuandoNaoTemDataBloqueio() throws ValidacaoException {
Date dataBloqueio = null;
RestricaoPessoa restricao = new RestricaoPessoa(new Brasileiro(), dataBloqueio,
OrigemRestricaoPessoa.SOCC, new TipoRestricaoPessoa());
restricao.validar();
}
@Test(expectedExceptions = PagamentoMaiorQueTotalAPagarException.class)
public void addPagamentoEspecieTendoValorMaiorQueDaProposta() throws
PagamentoMaiorQueTotalAPagarException,PagamentoExcedenteException {
proposta.valor(BigDecima.valueOf(50l);
PagamentoEspecie pagamento = new PagamentoEspecie(MoedaFixture.USD,
BigDecimal.valueOf(100l), proposta, new Venda());
proposta.addPagamento(pagamento);
}
32. Flexibilidade x Simplicidade
• Evite colocar código de lógica nos seus testes
• Nos testes, simplicidade é mais importante
que flexibilidade!
33. Flexibilidade x Simplicidade
Ruim:
@Test
public void shouldNavigateToPhotosPage() {
String baseUrl = "http://plus.google.com/";
Navigator nav = new Navigator(baseUrl);
nav.goToPhotosPage();
assertEquals(baseUrl + "/u/0/photos", nav.getCurrentUrl());
}
34. Flexibilidade x Simplicidade
Bom:
@Test
public void shouldNavigateToPhotosPage() {
Navigator nav = new Navigator(“http://plus.google.com/");
nav.goToPhotosPage();
assertEquals("http://plus.google.com//u/0/photos",
nav.getCurrentUrl()); // Oops!
}
35. “... if it seems like a simple change to code
causes excessively long changes to tests, that's
a sign that there's a problem with the tests. This
may not be so much that you are testing too
many things, but that you have duplication in
your tests….” - Kent Beck
Resiliência
36. Cobertura de Testes
• Porcentagem de cobertura é importante?
• A cobertura tem seus méritos!
37. Cobertura de Testes
• Encontrar código não testado
• Ter um senso de qual a situação real do
projeto
38. E o TDD?!
• O que é?
• No que facilita?
• Qual a dificuldade de usar?
• Polêmicas!
39. TDD
“If you don’t drive development with tests, what do
you drive it with? Speculation? Specifications (ever
notice that those two words come from the same
root?)”
Kent Beck - TDD by Example
40. …e os nossos clientes??
Como nossos clientes veem a questão dos
testes?
Eles acreditam neles tanto quanto nós
acreditamos?
Por que quando temos que priorizar algumas
coisas, acabamos por deixar os testes de lado
em algumas situações?
41. Conclusão
• Simplicidade é muito importante
• Se algum teste está difícil de manter, ele não
está bom
• Trate com carinho o código do teste
• Escreva seu código de teste pensando como
um código de business
42. O que ficou faltando falar...
• Muita coisa!
• Quando usar frameworks de mock (Mockito, EasyMock,
etc)
• Testes de tela: PhantonJS, Selenium, etc
• Minimização de bugs não-reproduzíveis
• Diferença entre fake, mock, stub e dummy
• Testes de carga
43. Referências
• Google Testing Blog
• Livro Code Complete
• Métodos ágeis: o que é flolclore e o que é real
• Is TDD dead?
Notas do Editor
Mostrar que não deve existir diferença entre qualidade de código de um teste para um código de “business”.
http://googletesting.blogspot.com.br/2014/05/testing-on-toilet-effective-testing.html
a resilient test doesn't have to change unless the purpose or behavior of the class being tested changes. Adding new behavior should only require adding new tests, not changing old ones. The original test above isn't resilient since you'll have to update it (and probably dozens of other tests!) whenever you add a new irrelevant constructor parameter. Moving these details into the helper method solved this problem.
* casos raros, como garantir que o sistema está lendo do cache e não do banco de dados
Detalhes desnecessários foram escondidos. O método makeAdditionComputation deixa mais claro.
O método newCalculator() abstrai toda a logica de como instanciar uma “calculadora” deixando o teste simples e claro para quem precisar le-lo
O método newCalculator() abstrai toda a logica de como instanciar uma “calculadora” deixando o teste simples e claro para quem precisar le-lo
Esperar para ver se o pessoal consegue identificar o que este teste faz! http://googletesting.blogspot.com.br/2014/10/testing-on-toilet-writing-descriptive.html
a resilient test doesn't have to change unless the purpose or behavior of the class being tested changes. Adding new behavior should only require adding new tests, not changing old ones. The original test above isn't resilient since you'll have to update it (and probably dozens of other tests!) whenever you add a new irrelevant constructor parameter. Moving these details into the helper method solved this problem.
Argumentar sobre essa frase, que é uma das justificativas para se usar TDD
Falar sobre o “is tdd dead”
Argumentar sobre essa frase, que é uma das justificativas para se usar TDD
Falar sobre o “is tdd dead”