Onde nenhum desenvolvedor 
jamais testou: 
Introduzindo testes unitários 
em código legado 
André Ricardo Barreto de Oliveira 
(“Arbo”) 
Core Software Engineer @ Liferay 
Agilidade@Recife 2014
discover.liferay.com
FINALMENTE 
Seu projeto vai adotar Agile
Em várias empresas perto de você: 
"O Gigantesco Projeto 
Feito Sem Agile" 
10+ anos em produção 
milhares de classes 
… e crescendo! 
milhões de linhas 
de código desktop / web / mobile dúzias de frameworks
Mas e os testes?
Introduzindo 
testes 
em código 
legado 
QA Testes 
Manuais Selenium 
Desenvolvedores 
Banco de Dados 
Spring 
Runner 
customizado
Introduzindo 
testes 
em código 
legado 
QA Testes 
Manuais Selenium 
Desenvolvedores 
Banco de Dados 
Spring 
Runner 
customizado 
?
Manual Prático 
de Paraquedismo 
"In the industry, legacy code 
is slang for difficult-­‐to-­‐change 
code that we don't understand. 
! 
To me, legacy code 
is simply code without tests." 
! 
-­‐ Michael C. Feathers
Testes de Caracterização 
Classe não tem testes? 
Escreva um teste que apenas 
documenta o comportamento atual.
Testes de Caracterização 
Feliz com a cobertura? 
Implemente a nova funcionalidade.
public class MassMailingServiceTest 
{ 
! 
@Test 
public void whatcangowrong() 
{ 
new MassMailingService(); 
} 
! 
}
new MassMailingService(); 
DatabaseException: 
! 
Você precisa estar conectado ao banco 
de dados para realizar esta operação!
public MassMailingService() 
{ 
this.limit = 
SettingsFromDatabaseService 
.getLimit(); 
} 
#FAIL 
Que fazer?
Alternativa 1
Alternativa 1 
1. Estudar a documentação do framework
Alternativa 1 
1. Estudar a documentação do framework
Alternativa 1 
1. Estudar a documentação do framework 
2. Instalar / importar / emprestar uma base de dados
Alternativa 1 
1. Estudar a documentação do framework 
2. Instalar / importar / emprestar uma base de dados 
3. Popular a base com os dados de teste
Alternativa 1 
1. Estudar a documentação do framework 
2. Instalar / importar / emprestar uma base de dados 
3. Popular a base com os dados de teste 
4. Logar na base
Alternativa 1 
1. Estudar a documentação do framework 
2. Instalar / importar / emprestar uma base de dados 
3. Popular a base com os dados de teste 
4. Logar na base 
5. Rodar o teste
Alternativa 2
Quando você pode alterar 
a classe de negócio... 
public MassMailingService( 
Settings settings) // interface 
{ 
this.limit = settings.getLimit(); 
}
@Test 
public void whatcangowrong() 
{ 
Settings s = 
Mockito.mock(Settings.class); 
Mockito.when(s.getLimit()) 
.thenReturn(42); 
new MassMailingService(s); 
}
Quando você não pode 
alterar a classe de negócio... 
@Test 
public void whatcangowrong() 
{ 
PowerMockito.mockStatic( 
SettingsFromDatabaseService.class); 
! 
PowerMockito.stub(method( 
SettingsFromDatabaseService.class, "getLimit")) 
.toReturn(42); 
! 
new MassMailingService(); 
}
Quando você não pode 
alterar a classe de negócio... 
@Test 
public void whatcangowrong() 
{ 
PowerMockito.mockStatic( 
SettingsFromDatabaseService.class); 
! 
PowerMockito.stub(method( 
SettingsFromDatabaseService.class, "getLimit")) 
.toReturn(42); 
! 
new MassMailingService(); 
}
public class MassMailingServiceTest 
{ 
! 
@Test 
public void send() 
{ 
new MassMailingService().send( 
new Message("Hello"), 
"andre.oliveira@liferay.com", 
"andre@arbo.com.br"); 
} 
! 
}
new MassMailingService().send( 
new Message("Hello"), 
"andre.oliveira@liferay.com", 
"andre@arbo.com.br"); 
30 segundos depois… 
Você possui 1 (uma) nova 
mensagem em sua caixa postal 
Você possui 1 (uma) nova 
mensagem em sua caixa postal
public void send( 
Message message, String... targets) 
{ 
for (Address address : targets) 
{ 
RealSMTPSender 
.send(message, address); 
} 
} 
#FAIL
@Test 
public void send() 
{ 
Sender s = Mockito.mock(Sender.class); 
Message message = new Message("Hello"); 
new MassMailingService(s).send(message, 
"andre.oliveira@liferay.com", 
"andre@arbo.com.br"); 
Mockito.verify(s).send(message, 
"andre.oliveira@liferay.com"); 
Mockito.verify(s).send(message, 
"andre@arbo.com.br"); 
}
@Test 
public void send() 
{ 
PowerMockito.mockStatic( 
RealSMTPSender.class); 
Message message = new Message("Hello"); 
new MassMailingService().send(message, 
"andre.oliveira@liferay.com", 
"andre@arbo.com.br"); 
PowerMockito.verifyStatic(); 
RealSMTPSender.send(message, 
"andre.oliveira@liferay.com"); 
PowerMockito.verifyStatic(); 
RealSMTPSender.send(message, 
"andre@arbo.com.br"); 
}
Testes unitários 
com isolamento 
(Martin Fowler) 
http://martinfowler.com/bliki/UnitTest.html
100% de cobertura? 
Sim, é possível!
if (service.result() > 5) { /* caso especial */ } 
@Test public void happyDay() { 
when(service.result()).thenReturn(1); 
// do it + assert happy day 
} 
! 
@Test public void casoEspecial() { 
when(service.result()).thenReturn(42); 
// do it + assert caso especial 
} 
Condicionais e casos especiais 
Cada if branch deriva um caso de teste
try { service.danger(); } 
catch (OpaException e) { /* caso especial */ } 
! 
@Test public void sorryDay() { 
when(service.danger()) 
.thenThrow(OpaException.class); 
// do it + assert caso especial 
} 
Tratamento de exceções 
Cada catch branch deriva um caso de teste
if (pessoa.idade() < 0) { 
throw new IdadeNegativaException(); 
} 
! 
@Expected(IdadeNegativaException.class) 
@Test public void wtf() { 
when(pessoa.idade()).thenReturn(-99); 
// do it (vai lançar a exception) 
} 
Validações 
Simulando entradas impossíveis com mocks
Resultado: todos os branches de execução 
guardados por testes... 
... e Coragem para evoluir 
código legado com Agile.
Happy testing! André de Oliveira 
“Arbo” 
twiKer.com/arbocombr 
andre.oliveira@liferay.com 
github.com/arboliveira/unit-­‐tests-­‐with-­‐isolaLon 
slidesha.re/1xCS1uY

Onde nenhum desenvolvedor jamais testou: Introduzindo testes unitários em código legado

  • 1.
    Onde nenhum desenvolvedor jamais testou: Introduzindo testes unitários em código legado André Ricardo Barreto de Oliveira (“Arbo”) Core Software Engineer @ Liferay Agilidade@Recife 2014
  • 2.
  • 3.
    FINALMENTE Seu projetovai adotar Agile
  • 4.
    Em várias empresasperto de você: "O Gigantesco Projeto Feito Sem Agile" 10+ anos em produção milhares de classes … e crescendo! milhões de linhas de código desktop / web / mobile dúzias de frameworks
  • 5.
    Mas e ostestes?
  • 7.
    Introduzindo testes emcódigo legado QA Testes Manuais Selenium Desenvolvedores Banco de Dados Spring Runner customizado
  • 8.
    Introduzindo testes emcódigo legado QA Testes Manuais Selenium Desenvolvedores Banco de Dados Spring Runner customizado ?
  • 9.
    Manual Prático deParaquedismo "In the industry, legacy code is slang for difficult-­‐to-­‐change code that we don't understand. ! To me, legacy code is simply code without tests." ! -­‐ Michael C. Feathers
  • 10.
    Testes de Caracterização Classe não tem testes? Escreva um teste que apenas documenta o comportamento atual.
  • 11.
    Testes de Caracterização Feliz com a cobertura? Implemente a nova funcionalidade.
  • 13.
    public class MassMailingServiceTest { ! @Test public void whatcangowrong() { new MassMailingService(); } ! }
  • 14.
    new MassMailingService(); DatabaseException: ! Você precisa estar conectado ao banco de dados para realizar esta operação!
  • 15.
    public MassMailingService() { this.limit = SettingsFromDatabaseService .getLimit(); } #FAIL Que fazer?
  • 16.
  • 17.
    Alternativa 1 1.Estudar a documentação do framework
  • 18.
    Alternativa 1 1.Estudar a documentação do framework
  • 19.
    Alternativa 1 1.Estudar a documentação do framework 2. Instalar / importar / emprestar uma base de dados
  • 20.
    Alternativa 1 1.Estudar a documentação do framework 2. Instalar / importar / emprestar uma base de dados 3. Popular a base com os dados de teste
  • 21.
    Alternativa 1 1.Estudar a documentação do framework 2. Instalar / importar / emprestar uma base de dados 3. Popular a base com os dados de teste 4. Logar na base
  • 22.
    Alternativa 1 1.Estudar a documentação do framework 2. Instalar / importar / emprestar uma base de dados 3. Popular a base com os dados de teste 4. Logar na base 5. Rodar o teste
  • 23.
  • 24.
    Quando você podealterar a classe de negócio... public MassMailingService( Settings settings) // interface { this.limit = settings.getLimit(); }
  • 25.
    @Test public voidwhatcangowrong() { Settings s = Mockito.mock(Settings.class); Mockito.when(s.getLimit()) .thenReturn(42); new MassMailingService(s); }
  • 26.
    Quando você nãopode alterar a classe de negócio... @Test public void whatcangowrong() { PowerMockito.mockStatic( SettingsFromDatabaseService.class); ! PowerMockito.stub(method( SettingsFromDatabaseService.class, "getLimit")) .toReturn(42); ! new MassMailingService(); }
  • 27.
    Quando você nãopode alterar a classe de negócio... @Test public void whatcangowrong() { PowerMockito.mockStatic( SettingsFromDatabaseService.class); ! PowerMockito.stub(method( SettingsFromDatabaseService.class, "getLimit")) .toReturn(42); ! new MassMailingService(); }
  • 30.
    public class MassMailingServiceTest { ! @Test public void send() { new MassMailingService().send( new Message("Hello"), "andre.oliveira@liferay.com", "andre@arbo.com.br"); } ! }
  • 31.
    new MassMailingService().send( newMessage("Hello"), "andre.oliveira@liferay.com", "andre@arbo.com.br"); 30 segundos depois… Você possui 1 (uma) nova mensagem em sua caixa postal Você possui 1 (uma) nova mensagem em sua caixa postal
  • 32.
    public void send( Message message, String... targets) { for (Address address : targets) { RealSMTPSender .send(message, address); } } #FAIL
  • 33.
    @Test public voidsend() { Sender s = Mockito.mock(Sender.class); Message message = new Message("Hello"); new MassMailingService(s).send(message, "andre.oliveira@liferay.com", "andre@arbo.com.br"); Mockito.verify(s).send(message, "andre.oliveira@liferay.com"); Mockito.verify(s).send(message, "andre@arbo.com.br"); }
  • 34.
    @Test public voidsend() { PowerMockito.mockStatic( RealSMTPSender.class); Message message = new Message("Hello"); new MassMailingService().send(message, "andre.oliveira@liferay.com", "andre@arbo.com.br"); PowerMockito.verifyStatic(); RealSMTPSender.send(message, "andre.oliveira@liferay.com"); PowerMockito.verifyStatic(); RealSMTPSender.send(message, "andre@arbo.com.br"); }
  • 35.
    Testes unitários comisolamento (Martin Fowler) http://martinfowler.com/bliki/UnitTest.html
  • 36.
    100% de cobertura? Sim, é possível!
  • 37.
    if (service.result() >5) { /* caso especial */ } @Test public void happyDay() { when(service.result()).thenReturn(1); // do it + assert happy day } ! @Test public void casoEspecial() { when(service.result()).thenReturn(42); // do it + assert caso especial } Condicionais e casos especiais Cada if branch deriva um caso de teste
  • 38.
    try { service.danger();} catch (OpaException e) { /* caso especial */ } ! @Test public void sorryDay() { when(service.danger()) .thenThrow(OpaException.class); // do it + assert caso especial } Tratamento de exceções Cada catch branch deriva um caso de teste
  • 39.
    if (pessoa.idade() <0) { throw new IdadeNegativaException(); } ! @Expected(IdadeNegativaException.class) @Test public void wtf() { when(pessoa.idade()).thenReturn(-99); // do it (vai lançar a exception) } Validações Simulando entradas impossíveis com mocks
  • 40.
    Resultado: todos osbranches de execução guardados por testes... ... e Coragem para evoluir código legado com Agile.
  • 41.
    Happy testing! Andréde Oliveira “Arbo” twiKer.com/arbocombr andre.oliveira@liferay.com github.com/arboliveira/unit-­‐tests-­‐with-­‐isolaLon slidesha.re/1xCS1uY