Skip to content

DCC603/tp02-unit-tests

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 

Repository files navigation

Aula Prática de TDD

Baseado na atividade do Prof. Marco Tulio Valente

Objetivo: reproduzir uma sessão de desenvolvimento de software usando TDD (Test-Driven Development).

Para isso, usaremos o mesmo exemplo do livro clássico de TDD, do Kent Beck. Especificamente, iremos reproduzir o exemplo do Cap. 1 até o Cap. 11 do livro (os capítulos são pequenos, com 3-4 páginas).

Faça o exercício com o modelo mental de TDD. Ou seja, para tirar proveito do exercício, é importante não apenas seguir o roteiro mecanicamente, mas também pensar sempre sobre as mudanças de fluxo de trabalho que ocorrem com TDD, os benefícios e desafios dessa técnica de escrita de código e testes.

Se necessário, estude antes o material visto em sala de aula. Caso tenha dúvidas, você pode consultar também o nosso livro, que explica os passos com mais detalhes.

Instruções:

  • Faça o fork deste repositório.

  • Siga o roteiro, passo a passo. Para facilitar, os passos são itemizadaos da seguinte forma: (1): Vermelho, (2): Verde, (3): Refactor. Isto é, exatamente os mesmos estados de desenvolvimento baseado em TDD.

  • Após cada passo, compile e rode os testes.

  • Nos passos marcados com COMMIT & PUSH, dê um commit e um push no seu repositório. Isso será usado no momento da correção, para garantir que seguiu a sequência do roteiro, passo-a-passo.

  • Importante: quando terminar, realize o pull request. A ausência do PR implicará na não correção do trabalho!

Roteiro

  • (1) Seja a seguinte classe e seu teste (exatamente o teste e a classe vistos em sala de aula, no final dos slides de TDD):
    class Dollar {
       int amount = 10;
       Dollar(int amount) {}			
       void times(int multiplier) {}
    }	

    public void testMultiplication() {
       Dollar five = new Dollar(5);
       five.times(2);
       assertEquals(10, five.amount);
    }
  • Faça um COMMIT & PUSH

  • (2) Dando uma implementação mais real para a classe Dollar:

class Dollar {
   int amount;
   Dollar(int amount) {
      this.amount= amount;
   }
   void times(int multiplier) {
      amount= amount * multiplier;
   }
}	
  • (1.1) Novo teste/feature: tornando Dollar imutável, i.e., agora, uma operação como times retorna um novo objeto e não modifica o objecto corrente:
public void testMultiplication() {
   Dollar five = new Dollar(5);
   Dollar product = five.times(2);
   assertEquals(10, product.amount);
   product = five.times(3);
   assertEquals(new Dollar(15), product);
}

Precisamos alterar Dolar.times() para compilar o novo teste:

class Dollar {
   ...
   Dollar times(int multiplier) {
      return new Dollar(amount * multiplier);
   }
}
  • (1.2) Apenas um refactoring (no teste), para ele ficar menor:
public void testMultiplication() {
   Dollar five = new Dollar(5);
   assertEquals(new Dollar(10), five.times(2));
   assertEquals(new Dollar(15), five.times(3));
}
  • (1.2.1) Dollar é a única classe usando a variável amount após as mudanças no teste. Logo, podemos alterar o seu modificador de acesso para private:
class Dollar {
   private int amount;
   ...
}
  • (1.3) Novo teste/feature: método equals, para testar se dois Dollar são iguais.
public void testEquality() {
   assertTrue(new Dollar(5).equals(new Dollar(5)));
   assertFalse(new Dollar(5).equals(new Dollar(6)));
}
  • (2.1) Implementando equals (método da classe Dollar):
public boolean equals(Object object)  {
   Dollar dollar = (Dollar) object;
   return amount == dollar.amount;
}
  • (1) e (2): Novo teste/feature: classe Franc (além de dólar, agora temos uma classe para armazenar francos suíços); Franc é basicamente uma cópia de Dollar, com os devidos ajustes de nomes.
public void testFrancMultiplication() {
   Franc five = new Franc(5);
   assertEquals(new Franc(10), five.times(2));
   assertEquals(new Franc(15), five.times(3));
}

class Franc {   
   private int amount;					
   Franc(int amount) {      
      this.amount= amount;
    }					
    Franc times(int multiplier)  {      
       return new Franc(amount * multiplier);					
    }   
    public boolean equals(Object object) {					
       Franc franc = (Franc) object;      
       return amount == franc.amount;					
     }					
}
  • (3.1) Refactor: vamos criar uma superclasse Money; subir amount para ela (como protected) e também subir equals (com alguns ajustes no código). Ou seja, ambos vão ser deletados das subclasses Dollar e Franc e movidos para Money. Depois, não se esqueça de fazer Dollar e Franc herdarem de Money.
class Money  {
   protected int amount;
   
   public boolean equals(Object object)  {
      Money money = (Money) object;
      return amount == money.amount;
   }   
}
  • (3.2) Mais alguns testes, que vão passar:
public void testEquality() {
   assertTrue(new Dollar(5).equals(new Dollar(5)));
   assertFalse(new Dollar(5).equals(new Dollar(6)));
   assertTrue(new Franc(5).equals(new Franc(5)));
   assertFalse(new Franc(5).equals(new Franc(6)));
}
  • Faça um COMMIT & PUSH

  • (1) Melhorando testEquality (só adicionando último assert, no código abaixo), que compara Franc com Dollar.

public void testEquality() {
   assertTrue(new Dollar(5).equals(new Dollar(5)));
   assertFalse(new Dollar(5).equals(new Dollar(6)));
   assertTrue(new Franc(5).equals(new Franc(5)));
   assertFalse(new Franc(5).equals(new Franc(6)));
   assertFalse(new Franc(5).equals(new Dollar(5)));
}
  • (2) Agora equals verifica se as classes dos objetos comparados são iguais.
class Money {
   ...
   public boolean equals(Object object) {
      Money money = (Money) object;
      return amount == money.amount && getClass().equals(money.getClass());
   }
}				
  • (3) Refactoring: times agora retorna Money (era Dollar e Franc). Basicamente, estamos preparando o terreno para daqui a pouco remover essas classes. O código delas é muito parecido; e o objetivo do passo de Refactor em TDD é principalmente remover duplicação.
class Dollar {
   ...
   Money times(int multiplier)  {
      return new Dollar(amount * multiplier);
   }								
}    

class Franc {
   ...
   Money times(int multiplier)  {
      return new Franc(amount * multiplier);
   }								
}    
  • (1) e (2) Novo teste/feature - método fábrica, chamado Money.dollar; mais uma passo para começar a eliminar a classe Dollar.
public void testMultiplication() {
   Money five = Money.dollar(5);
   assertEquals(Money.dollar(10), five.times(2));
   assertEquals(Money.dollar(15), five.times(3));
}

Adicionalmente, veja que tivemos que tornar Money abstrata, para compilar (com o método times abstrato):

abstract class Money {
   ...
   static Dollar dollar(int amount)  {
      return new Dollar(amount);
   }
	
   abstract Money times(int multiplier);  
}
  • (1) e (2): Novo teste/feature - Money.franc (fábrica de Francos).
public void testEquality() {
   assertTrue(Money.dollar(5).equals(Money.dollar(5)));
   assertFalse(Money.dollar(5).equals(Money.dollar(6)));
   assertTrue(Money.franc(5).equals(Money.franc(5)));
   assertFalse(Money.franc(5).equals(Money.franc(6)));
   assertFalse(Money.franc(5).equals(Money.dollar(5)));
}

public void testFrancMultiplication() {
   Money five = Money.franc(5);
   assertEquals(Money.franc(10), five.times(2));
   assertEquals(Money.franc(15), five.times(3));
}

class Money {
   ...
   static Money dollar(int amount)  {
      return new Dollar(amount);
   }
   
   static Money franc(int amount) {
      return new Franc(amount);
    }
} 
  • Faça um COMMIT & PUSH

  • (1) e (2): Novo teste/feature: currency().

public void testCurrency() {
   assertEquals("USD", Money.dollar(1).currency());
   assertEquals("CHF", Money.franc(1).currency());
}

abstract class Money {
   ...
   abstract String currency();
} 

class Franc extends Money {
   ...
   String currency() {
      return "CHF";
   }
} 

class Dollar extends Money {
    ...
    String currency() {
       return "USD";
    }
} 
  • (3.1): Refactor: adicionando um atributo currency, em Franc e Dollar.
class Franc extends Money {
   private String currency;
	
   Franc(int amount) {
      this.amount = amount;
      currency = "CHF";
   }
   
   String currency() {
      return currency;
   }
}

class Dollar extends Money {
   private String currency;
	
   Dollar(int amount) {
      this.amount = amount;
      currency = "USD";
   }
   
   String currency() {
      return currency;
   }
}
  • (3.2) Refactoring: subir currency para Money (como protected).
abstract class Money {
   ...
   protected String currency;
   ...
   String currency() {
      return currency;
   }
} 

class Franc extends Money {	
   Franc(int amount) {
      this.amount = amount;
      currency = "CHF"; 
   }
   ...
}

class Dollar extends Money {
   Dollar(int amount)  {
      this.amount = amount;
      currency = "USD";
   }
   ...
}
  • (3.3.) Refactor: times agora retorna Money, tanto em Dollar, como em Franc.
class Dollar {
   ...
   Money times(int multiplier)  {
      return Money.dollar(amount * multiplier);
   }								
}    

class Franc {
   ...
   Money times(int multiplier)  {
      return Money.franc(amount * multiplier);
   }								
}    
  • (3.4) Refactor: construtores ganham um parâmetro currency.
abstract class Money {
   ...
   static Money dollar(int amount)  {
      return new Dollar(amount, "USD");
   }
   
   static Money franc(int amount) {
      return new Franc(amount, "CHF");
   }
} 

class Franc extends Money {
	
   Franc(int amount, String currency) {
      this.amount = amount;
      this.currency = currency;
   }
}

class Dollar extends Money {
	
   Dollar(int amount, String currency)  {
      this.amount = amount;
      this.currency = currency;
   }
}
  • (3.5) Subir construtores para Money:
abstract class Money {
   private String currency; 

   static Money dollar(int amount)  {
      return new Dollar(amount, "USD");
   }

   static Money franc(int amount) {
      return new Franc(amount, "CHF");
   }

   Money(int amount, String currency) {
      this.amount = amount;
      this.currency = currency;
   }
}

class Franc extends Money {	
   Franc(int amount, String currency) {
      super(amount, currency);
   }
     
   Money times(int multiplier)  {
      return Money.franc(amount * multiplier);
   }
}

class Dollar extends Money {	
   Dollar(int amount, String currency)  {
      super(amount, currency);
   }
	
   Money times(int multiplier)  {
      return Money.dollar(amount * multiplier);
   }
}
  • Faça um COMMIT & PUSH

  • (1) e (2) Novo teste/feature: comparando Money e Franc.

public void testDifferentClassEquality() {
   assertTrue(new Money(10, "CHF").equals(new Franc(10, "CHF")));
}

A implementação inclui ainda: tornar Money uma classe concreta (remover abstract); modificar o equals (com isso, moedas de classes diferente podem ser iguais, basta que amount e currency sejam iguais; subir times, com alguns ajustes, para Money.

class Money {
   ...
   static Money dollar(int amount)  {
      return new Dollar(amount, "USD");
   }
	
   static Money franc(int amount) {
      return new Franc(amount, "CHF");
   }
    
   Money(int amount, String currency) {
      this.amount = amount;
      this.currency = currency;
   }
	
   public boolean equals(Object object) {
      Money money = (Money) object;
      return amount == money.amount && currency().equals(money.currency());
   }
	
   Money times(int multiplier) {
      return new Money(amount * multiplier, currency);
   }
}
  • (3) Refactor: Alterar o seguinte teste:
public void testEquality() {
   assertTrue(Money.dollar(5).equals(Money.dollar(5))); 
   assertFalse(Money.dollar(5).equals(Money.dollar(6)));
   assertFalse(Money.franc(5).equals(Money.dollar(5)));
}	

Remover classes Dollar e Franc. Alterar as referências na classe Money:

class Money {
   static Money dollar(int amount)  {
      return new Money(amount, "USD");
   }

   static Money franc(int amount) {
      return new Money(amount, "CHF");
   }
   ...
}

E remover testDifferentClassEquality() e testFrancMultiplication().

  • Faça um COMMIT & PUSH

About

Aula Prática de TDD

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors