Como escrever bons testes! - Dex transforming days
-
Upload
danilo-pereira-de-luca -
Category
Software
-
view
197 -
download
0
Transcript of Como escrever bons testes! - Dex transforming days
“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
• 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”
Motivação
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
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?
}
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?
}
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?
}
Como conseguir um teste bom?
Bom:
@Test
public void shouldPerformAddition() {
Calculator calculator = newCalculator();
int result = calculator.doComputation(makeAdditionComputation(2,3));
assertEquals(5, result);
}
Como conseguir um teste bom?
Bom:@Test
public void shouldPerformAddition() {
Calculator calculator = newCalculator();
int result = calculator.doComputation(makeAdditionComputation(2, 3));
assertEquals(5, result);
}
Como conseguir um teste bom?
Bom:@Test
public void shouldPerformAddition() {
Calculator calculator = newCalculator();
int result = calculator.doComputation(makeAdditionComputation(2, 3));
assertEquals(5, result);
}
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));
}
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));
}
• Preparação dos cenários
• Builder e Fluent
• Load-data
• Como identificar o que é bom ser testado
(tudo?). Como criar estes testes?
Quais as dificuldades mais comuns?
• É o local mais comum para ocorrer duplicação
de código
• Favorece o reuso desnecessário de cenários
• O que podemos fazer?
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
Preparação dos cenários
Sem Builder e sem Fluentpublic 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
}
}
Builder com Fluentpublic 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
}
}
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;
}
}
Builder com Fluent
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?
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.
Load-datapublic 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);
}
}
Load-data
@Test
public void buscarRestricaoPorPessoaQuandoNaoExistirRestricaoParaAPessoa()
{
RestricaoPessoa restricao =
repository.buscarPorPessoa(PessoaFixture.JamesTKirk.toEntidade());
Assert.assertNull(restricao);
}
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?
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
}
}
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.
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!
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());
}
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());
}
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);
}
Flexibilidade x Simplicidade
• Evite colocar código de lógica nos seus testes
• Nos testes, simplicidade é mais importante
que flexibilidade!
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());
}
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!
}
“... 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
Cobertura de Testes
• Encontrar código não testado
• Ter um senso de qual a situação real do
projeto
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
…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?
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
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
Referências
• Google Testing Blog
• Livro Code Complete
• Métodos ágeis: o que é flolclore e o que é real
• Is TDD dead?