onde nenhum desenvolvedor jamais testou: introduzindo testes unitários em código legado
DESCRIPTION
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 2014TRANSCRIPT
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
milhões de linhas de código desktop / web / mobile dúzias de frameworks
… e crescendo!
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(); }!
}
DatabaseException:!
Você precisa estar conectado ao banco de dados para realizar esta operação!
new MassMailingService();
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
public MassMailingService( Settings settings) // interface{ this.limit = settings.getLimit();}
Quando você pode alterar a classe de negócio...
@Testpublic void whatcangowrong(){ Settings s = Mockito.mock(Settings.class); Mockito.when(s.getLimit()) .thenReturn(42); new MassMailingService(s);}
@Testpublic 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...
@Testpublic 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...
public class MassMailingServiceTest{! @Test public void send() { new MassMailingService().send( new Message("Hello"), "[email protected]", "[email protected]"); }!}
new MassMailingService().send( new Message("Hello"), "[email protected]", "[email protected]");
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
@Testpublic void send(){ Sender s = Mockito.mock(Sender.class); Message message = new Message("Hello"); new MassMailingService(s).send(message, "[email protected]", "[email protected]"); Mockito.verify(s).send(message, "[email protected]"); Mockito.verify(s).send(message, "[email protected]");}
@Testpublic void send(){ PowerMockito.mockStatic( RealSMTPSender.class); Message message = new Message("Hello"); new MassMailingService().send(message, "[email protected]", "[email protected]"); PowerMockito.verifyStatic(); RealSMTPSender.send(message, "[email protected]"); PowerMockito.verifyStatic(); RealSMTPSender.send(message, "[email protected]");}
Testes unitários com isolamento
http://martinfowler.com/bliki/UnitTest.html
(Martin Fowler)
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
github.com/arboliveira/unit-‐tests-‐with-‐isolaLon
_