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

Preview:

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 2014

Citation preview

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"), "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

@Testpublic 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");}

@Testpublic 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

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”

andre.oliveira@liferay.com

twiKer.com/arbocombr

github.com/arboliveira/unit-­‐tests-­‐with-­‐isolaLon

_