37
Escrevendo testes unitários para código legado: técnicas de isolamento André Ricardo Barreto de Oliveira (“Arbo") Core Software Engineer @ Liferay

Escrevendo testes unitários para código legado: técnicas de isolamento

Embed Size (px)

Citation preview

Escrevendo testes unitários para código legado: técnicas de isolamento

André Ricardo Barreto de Oliveira (“Arbo")Core Software Engineer @ Liferay

discover.liferay.com/tdc2014

Banco de Dados +

Objeto Relacional

Teste automatizado

Orientação a Objetos

Ambientes de Desenvolvimento

Servidores de Aplicação

+ REST + SOAP

Distribuição + Nuvem

Bibliotecas + Frameworks

Refactoring + Análise EstáticaProfiling

Comunidade++

2015: 20 anos

Integração Contínua Programação

Funcional + Scala

Em várias empresas perto de você:

"O Gigantesco Projeto Escrito em Java"

10+ anos em produção

milhares de classes

milhões de linhas de código desktop / web / mobile

centenas de jars, wars, ears

dúzias de frameworks

… e crescendo!

Mas e os testes?

“Veja bem…"

Projetos Java Gigantescos

QATestes

ManuaisSelenium

Desenvolvedores

Banco de Dados

Spring

Runner customizado

SEM TESTES?

SEM PROBLEMAS :)

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

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...

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

Isolamento e legado

Testes de Caracterização? Código legado, sem testes?

Isolamento vai te ajudar

Isolamento e legado

Código novo, testes novos?

Use o bom senso, ou…

"TDD is dead. Long live testing" http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html

!"Is TDD dead?" http://martinfowler.com/articles/is-tdd-dead/

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: cobertura de todos os branches de execução

!

Novas funcionalidades /Refactorings

Bugfixes: cobertura mínima

• Rastrear a linha de código do bug • Escrever teste para o fragmento de lógica • Mock mínimo que reproduz o problema • Protip: Extrair método testável

Testes unitários com isolamento: benefícios

2

3

4

5

6

Rodam em < 1 segundo (como integração chegavam a 30+)

Dispensam preparar base de dados ou serviços

Fácil conseguir 100% de cobertura

Blindagem contra colaboradores mal comportados

Simular exceptions, casos especiais e dados ruins

Modularização de Projetos Java Gigantescos

1

Happy testing!André Oliveira

[email protected]

twitter.com/arbocombr

github.com/arboliveira/unit-tests-with-isolation