Como escrever bons testes! - Dex transforming days

43
Como escrever bons testes! por Danilo De Luca e Dherik Barison

Transcript of Como escrever bons testes! - Dex transforming days

Como escrever bons testes!por Danilo De Luca e Dherik Barison

“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

• Performance

• Manutenabilidade

• Clareza

• Resiliência

• Precisão

O que faz um teste ser bom?

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.

O que testar?

• Mesmo com TDD, esta pergunta é válida;

• Teste comportamentos e não métodos;

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

• Porcentagem de cobertura é importante?

• A cobertura tem seus méritos!

Cobertura de Testes

• Encontrar código não testado

• Ter um senso de qual a situação real do

projeto

E o TDD?!

• O que é?

• No que facilita?

• Qual a dificuldade de usar?

• Polêmicas!

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