exemplo de desenvolvimento com testes (junit - estudo de caso) alfredo goldman
TRANSCRIPT
Exemplo de desenvolvimento com testes
(JUNIT - estudo de caso)
Alfredo Goldman
Problema com testesTodos sabem: devem ser escritos;Poucos o fazem, e por quê não ?
Estou com muita pressaMas, isto cria um círculo vicioso:
menos testesmenos produtividademenos estabilidade
mais pressão
Como quebrar este cicloCriando um ambiente simples de testesDepois de fazer os primeiros testes
o hábito vem para ficar
Vamos mostrar como seria um desenvolvimento ideal.... Através do JUnit...
O programaUm sistema para representar
diversas moedas;
Para começar: algo simples.
class Money { private int fAmount; private String fCurrency;
public Money(int amount, String currency) { fAmount = amount; fCurrency = currency; }
public int amount() { return fAmount; }
public String currency() { return fCurrency; }}
Para somar dois Moneys da mesma moeda (currency):
public Money add(Money m) { return new Money(amount()+m.amount(), currency());}
Questão de hábitoCode a little, test a little, code a little,
test a little....
Já temos um objeto, vamos testá-lo !!
No JUnit os testes devem ser subclasses de TestCase
public class MoneyTest extends TestCase { //… public void testSimpleAdd() { Money m12CHF= new Money(12, "CHF"); // (1) Money m14CHF= new Money(14, "CHF"); Money expected= new Money(26, "CHF"); Money result= m12CHF.add(m14CHF); // (2) Assert.assertTrue(expected.equals(result)); // (3) }}
O testSimpleAdd() consiste em:
Código para criar os objetos;
Código para usar os objetos;
Código para verificar os resultados.
Falta fazer a sobrecarga de equals
public void testEquals() { Money m12CHF= new Money(12, "CHF"); Money m14CHF= new Money(14, "CHF");
Assert.assertTrue(!m12CHF.equals(null)); Assert.assertEquals(m12CHF, m12CHF); Assert.assertEquals(m12CHF, new Money(12, "CHF")); // (1) Assert.assertTrue(!m12CHF.equals(m14CHF));}
// lembrete: o equals do object volta true se os// objetos comparados são o mesmo.
Mas antes um teste para o equals
Agora sim
public boolean equals(Object anObject) { if (anObject instanceof Money) { Money aMoney = (Money) anObject; return aMoney.currency().equals(currency()) && amount() == aMoney.amount(); } return false;}// faltou sobrecarregar o método hashCode...
Mas, já apesar dos testes serem pequenos já há código duplicado...
public class MoneyTest extends TestCase { private Money f12CHF; private Money f14CHF;
protected void setUp() { f12CHF= new Money(12, "CHF"); f14CHF= new Money(14, "CHF"); }}
Agora os testes podem ser rescritos como:
public void testEquals() { assert(!f12CHF.equals(null)); assertEquals(f12CHF, f12CHF); assertEquals(f12CHF, new Money(12, "CHF")); assert(!f12CHF.equals(f14CHF));}
public void testSimpleAdd() { Money expected= new Money(26, "CHF"); Money result= f12CHF.add(f14CHF); assert(expected.equals(result));}
Próximos passosDefinir como rodar um teste
individual;
Definir como rodar uma seqüência de testes.
// forma estática, com classe interior
TestCase test= new MoneyTest("simple add") { public void runTest() { testSimpleAdd(); }};
// forma dinâmica, usa reflexão
TestCase test= new MoneyTest("testSimpleAdd");
Pode-se automatizar testes Criando uma seqüência de testes
public static Test suite() { TestSuite suite= new TestSuite(); suite.addTest(new MoneyTest("testEquals")); suite.addTest(new MoneyTest("testSimpleAdd")); return suite;}
Pode-se automatizar testes Ou apenas:
public static Test suite() {return new TestSuite(MoneyTest.class);}
Agora, um pouco de JUnit na prática.
Continuando o projetoDeve-se poder guardar diversos tipos de moeda
class MoneyBag { private Vector fMoneis= new Vector();
MoneyBag(Money m1, Money m2) { appendMoney(m1); appendMoney(m2); }
MoneyBag(Money bag[]) { for (int i= 0; i < bag.length; i++) appendMoney(bag[i]); }}
protected void setUp() { f12CHF= new Money(12, "CHF"); f14CHF= new Money(14, "CHF"); f7USD= new Money( 7, "USD"); f21USD= new Money(21, "USD"); fMB1= new MoneyBag(f12CHF, f7USD); fMB2= new MoneyBag(f14CHF, f21USD);}
Para os testes deve se criar também objetos do novo tipo
public void testBagEquals() { assert(!fMB1.equals(null)); assertEquals(fMB1, fMB1); assert(!fMB1.equals(f12CHF)); assert(!f12CHF.equals(fMB1)); assert(!fMB1.equals(fMB2));}
Devem se criar novos testes, mas os testes antigos continuam lá
E devem continuar funcionando...
public Money add(Money m) { if (m.currency().equals(currency()) ) return new Money(amount() + m.amount(), currency()); return new MoneyBag(this, m);}// ops MoneyBag != Money....
Agora podemos melhorar o método add
Agora existem duas representações de dinheiro...
interface IMoney { public abstract IMoney add(IMoney aMoney); //…}
Mas, ainda não temos testes para tipos mistos...
public void testMixedSimpleAdd() { // [12 CHF] + [7 USD] == {[12 CHF][7 USD]} Money bag[]= { f12CHF, f7USD }; MoneyBag expected= new MoneyBag(bag); assertEquals(expected, f12CHF.add(f7USD)); }Os outros testes seguem o mesmo padrão:
testBagSimpleAdd - soma MoneyBag com Money testSimpleBagAdd - soma Money com MoneyBag testBagBagAdd - soma dois MoneyBags
Mais testes estão disponíveis:
public static Test suite() { TestSuite suite= new TestSuite(); suite.addTest(new MoneyTest("testMoneyEquals")); suite.addTest(new MoneyTest("testBagEquals")); suite.addTest(new MoneyTest("testSimpleAdd")); suite.addTest(new MoneyTest("testMixedSimpleAdd")); suite.addTest(new MoneyTest("testBagSimpleAdd")); suite.addTest(new MoneyTest("testSimpleBagAdd")); suite.addTest(new MoneyTest("testBagBagAdd")); return suite;}
Agora sim vamos implementá-los...
class Money implements IMoney { public IMoney add(IMoney m) { return m.addMoney(this); } //…}class MoneyBag implements IMoney { public IMoney MoneyBag.add(IMoney m) { return m.addMoneyBag(this); } //…}
//… IMoney addMoney(Money aMoney); IMoney addMoneyBag(MoneyBag aMoneyBag);}
Em Money.
public IMoney addMoney(Money m) { if (m.currency().equals(currency())) return new Money(amount()+m.amount(), currency()); return new MoneyBag(this, m);}
public IMoney addMoneyBag(MoneyBag s) { return s.addMoney(this);}
Em MoneyBag.
public IMoney addMoney(Money m) { return new MoneyBag(m, this);}
public IMoney addMoneyBag(MoneyBag s) { return new MoneyBag(s, this);}
Surge um problema.... E se retira-se 12CHF de um MoneyBag com12CHF ???
Primeiro o teste...
public void testSimplify() { // {[12 CHF][7 USD]} + [-12 CHF] == [7 USD] Money expected= new Money(7, "USD"); assertEquals(expected, fMS1.add(new Money(-12, "CHF")));}
Depois o código.
public IMoney addMoney(Money m) { return (new MoneyBag(m, this)).simplify();}
public IMoney addMoneyBag(MoneyBag s) { return (new MoneyBag(s, this)).simplify();}
private IMoney simplify() { if (fMonies.size() == 1) return (IMoney)fMonies.firstElement() return this;}
Desenvolvimento com testesTestes devem ser escritos assim que
possível;Testes devem ser adaptados segundo
as mudanças;Deixe os testes antigos rodando;Quando surgem novas idéias (simplify),
crie testes, veja se funcionam, e se necessário altere o código.