escrevendo testes unitários para código legado: técnicas de isolamento
TRANSCRIPT
Escrevendo testes unitários para código legado: técnicas de isolamento
André Ricardo Barreto de Oliveira (“Arbo")Core Software Engineer @ Liferay
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!
Projetos Java Gigantescos
QATestes
ManuaisSelenium
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.
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
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
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/
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
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
twitter.com/arbocombr
github.com/arboliveira/unit-tests-with-isolation