O documento discute técnicas de teste de software, como o desenvolvimento guiado por testes (TDD) e o uso de frameworks de mock como o Mockito. Ele explica como criar mocks e espiões para isolar unidades de teste e validar comportamentos. Também aborda ferramentas do Spring para testar controllers e simular requisições HTTP.
2. Estilo de desenvolvimento guiado pela criação
de testes que validam a funcionalidade
implementada antes da criação do código de
produção
◦ Código mais simples
◦ Algoritmos confiáveis
◦ Menor custo de manutenção
3.
4. Aumento de complexidade em cenários de
testes funcionais (interface de usuários,
repositórios)
Apoio gerencial
Testes falhos causam programas falhos
Testes de cobertura
Lacunas de testes
Falso senso de segurança (TDD não é bala de
prata)
5. Testes requerem visibilidade dos métodos
Encapsulamento requer esconder métodos
Use nomes de pacotes iguais
◦ Classe de produção está no pacote com.foo.bar
◦ Classe de teste deve ficar no pacote com.foo.bar
Use modificadores protected
e package (default)
7. Framework de Mock
◦ Simples
◦ Claro
◦ Intuitivo (depois que você entende sua lógica)
◦ Versão atual 1.9.0
◦ Documentação e notícias
http://code.google.com/p/mockito/
8. @Test
public void myTest() {
SomeObject myMock = Mockito.mock(SomeObject.class);
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations =
{"/applicationContext.xml"})
public class SourceCheckerTest {
@Autowired
SourceChecker sourceChecker;
@Mock
SourceRepository mockRepository;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
9. Adiciona um comportamento a um dado
método de um mock
public SourceVO getSource(RequestVO request) {
SourceVO sourceVO = null;
sourceVO = repository.loadById(request.getSourceId());
…
}
@Test
public void procuraSourceExistenteTest() throws RepositoryException {
SourceVO sourceVO = getSourceVO();
String sourceId = sourceVO.getId().toString();
Mockito.when(mockRepository.loadById(sourceId)).thenReturn(sourceVO);
RequestVO request = Given.request();
request.setSourceId(sourceId);
SourceVO source = sourceChecker.getSource(request);
…
}
12. Mock é feito com a comparação de objetos,
muitas vezes não temos o objeto exato para
se comparar
Mockito.when(lotteryMock.theChosenOne(Mockito.anyMap())).thenReturn(categId);
Mockito.any();
Mockito.any(CampaignVO.class);
Mockito.anyBoolean();
Mockito.anyInt();
Mockito.anyCollection();
Mockito.anyCollectionOf(String.class);
Mockito.anyList();
Mockito.anyListOf(Integer.class);
Mockito.anyVararg();
Matchers customizados também podem ser
criados mas a sua utilização é muito rara
13. Métodos void são um caso a parte para o
Mockito pois o when recebe o método a ser
mockado como parâmetro e o compilador
java não aceita isso
public static void main(String[] args) {
System.out.println(getNum()); //OK
System.out.println(getVoid()); // ERROR
}
private static Integer getNum() {
return 5;
}
private static void getVoid() { }
14. Uso dos métodos doThrow e
doCallRealMethod
Mockito.doThrow(NullPointerException.class).when(sourceRepository).loadById("123456");
Mockito.doCallRealMethod().when(sourceRepository).loadById("123456");
15. Os mocks conseguem retornar objetos
controlados mas as vezes deseja-se
interceptar a chamada para alterar a resposta,
verificar os parâmetros passados e executar
ou não o método real para isso existe o
Answer
Evite usar answer, procure soluções mais
simples.
16. Dentro do AdServer o Answer foi usado para
validar a passagem de argumentos em
métodos complexos
public void makeSearch(CampaignVO campaign) {
changeCampaignName(campaign);
changeOtherValue(campaign);
repository.search(campaign); // What are the campaign's attributes?
}
Ex.: Desejo validar a campanha passada no repositório já que
essa campanha pode ter sido alterada por métodos
intermediários
17. Mockito.doAnswer(new Answer<Map<Long, Double>>() {
public Map<Long, Double> answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
// valida país recebido
List<AdVO> adVO = (List<AdVO>) args[0];
Assert.assertTrue(adVO.size() == 1);
Assert.assertEquals("728_90", adVO.get(0).getDimension());
return values;
}
}).when(adSelectorSpy).createAdsValues(adList);
Valido se o primeiro argumento passado possui um elemento
e se esse elemento tem um valor específico
18. O que acontece se você deseja controlar
apenas uma parte de um objeto?
◦ Ex.: Deseja-se testar um método que chama outro
método dentro da mesma classe
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations =
{"/applicationContext.xml"})
public class SourceCheckerTest {
@Autowired
SourceChecker sourceChecker;
SourceChecker checkerSpy;
@Before
public void before() {
checkerSpy = Mockito.spy(sourceChecker);
}
Espiamos o comportamento
de um método real
19. Um pouco de teoria
◦ Quando um método é mockado ele é executado, porém
como ele não existe, não há problema
◦ Mockar um método de um objeto espionado faz o método
ser executado, porém como ele é real isso pode causar
problemas
◦ Spies devem usar métodos doThrow, doReturn e doNothing
◦ MOCKS NUNCA executam métodos reais até que digamos o
contrário (thenCallRealMethod ou doCallRealMethod), SPIES
SEMPRE executam métodos reais até que digamos o
contrário (doNothing)
21. O Mockito também consegue validar o
comportamento dos métodos reais,
verificando quantas vezes um método foi ou
não chamado, em qual ordem, etc
A validação de comportamento pode ser
muito útil em classes de transformações ou
validadores
22. O Mockito também consegue validar o
comportamento dos métodos reais,
verificando quantas vezes um método foi ou
não chamado, em qual ordem, etc
A validação de comportamento pode ser
muito útil em classes de transformações ou
validadores
23. Valido que uma chamada não ocorreu
@Autowired
TestFilter filter;
TestFilter filterMock;
@Test
public void filtroNaoPreValidadoTest() {
filterMock = Mockito.spy(filter);
Mockito.doReturn(false).when(filterMock.preValidate(Mockito.any(CampaignVO.class)));
List<CampaignVO> campaignList = createCampaignList();
filterMock.doFilter(createSource(), campaignList);
// Não passou na prevalidação não deve chamar o filtro
Mockito.verify(filterMock, Mockito.never()).filter(Mockito.any(CampaignVO.class),
Mockito.any(SourceVO.class));
}
25. Testes muitas vezes obriga a mudar atributos
privados de uma classe, como suas
dependências.
O Spring Testing possui uma classe chamada
ReflectionTestUtils com vários métodos úteis
// Adiciona o objeto sourceCheckerMock dentro do objeto businessMock,
// sendo esse atributo chamado “sourceChecker”
ReflectionTestUtils.setField(target, “atrributeName", objectToInsert);
Boolean slaveCrash = (Boolean) ReflectionTestUtils.getField(target, “attributeToRead");
26. O Spring Testing também possui algumas
ferramentas para se testar controllers e
emular chamadas HTTP
27. @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/applicationContext.xml", "/springmvc-servlet.xml"})
public class CacheStatusControllerTest {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private CacheStatusController controller;
private MockHttpServletRequest request = new MockHttpServletRequest();
private MockHttpServletResponse response = new MockHttpServletResponse();
private AnnotationMethodHandlerAdapter handler;
Deve-se carregar o contexto da aplicação e o do MVC
Deve-se carregar o controller necessário
Deve-se criar um handler e um Mock do Request e Response
28. @Before
public void before() {
// Recupera handler para a chamada ao Controller
handler = applicationContext.getBean(AnnotationMethodHandlerAdapter.class);
// Habilita recuperação do conteúdo do response
response.setOutputStreamAccessAllowed(true);
}
O Handler deve ser associado o Handler usado no Spring (talvez seja um
pouco chato descobrir isso)
Caso deseje-se ler o conteúdo do response, o que é o mais comum, deve-se
setar o acesso ao OutputStream
29. @Test
public void validateCacheStatus() throws Exception {
// Prepara método e url de chamada
request.setMethod("GET");
request.setRequestURI("/status/cache.html");
request.setParameter(“update", “true");
handler.handle(request, response, controller);
Assert.assertEquals("Informações incorretas", expectedResponse(),
response.getContentAsString());
}
O request deve ser setado com o método desejado (GET,POST,PUT,etc)
A URL de ser setada na URI do request
Caso haja parâmetros esses devem ser setados no request
Para executar a chamada utilize o método handle do Handler capturado
Para ler a resposta, caso tenha setado essa opção no response, utilize o
método getContentAsString (ou outro formato desejado)
30. Quando utilizar o Spring como seu framework de
Injeção de Dependência, prefira sempre as
configurações por anotações ao invés do XML,
porém vale lembrar que essa última configuração
sobrescreve qualquer anotação feita.
Certos cenários de teste necessitam de situações
especiais, não hesite em configurar o contexto
para que a situação seja contemplada. Ex.: Ligar
ou desligar transações, ler outro arquivo de
propriedades, ligar ou desligar aspectos, etc.