JMock2 - conceito

Olá pessoal.

Estou começando a utilizar JMock + JUnit para criação dos meus testes unitários, porém ainda não consegui entender o fundamento completo da utilização de mocks.

Abaixo eu coloco uma classe de teste que implementei para realizar testes para um serviço que tenho no meu sistema. Este serviço por sua vez utiliza uma instância do EntityManager que foi injetada pelo contexto do Spring.

Por isso comecei a utilizar o JMock, ou seja, para que eu não precise me preocupar com EntityManager e fazer meus testes independentemente de o sistema estar conectado ao banco ou não.

Porém mesmo com a classe abaixo, eu não consegui entender o quanto isso prova que meu serviço realmente é válido durante o teste. Principalmente considerando o método gravarUsuario(), pois para que um usuário seja válido na gravação é necessário que uma série de atributos estejam preenchidos como login, senha e etc.

Alguém poderia me ajudar a entender melhor este cenário?

Obrigado.

public class CadastroUsuarioServiceTest {

	JUnit4Mockery context = new JUnit4Mockery() {{
		setImposteriser(ClassImposteriser.INSTANCE);
	}};
	
	EntityManager em = context.mock(EntityManager.class);
	CadastroUsuarioServiceImpl service;
	
	@Before
	public void setUp() {
		service = new CadastroUsuarioServiceImpl();
		service.setEm(em);
	}
	
	@After
	public void tearDown() {
		service = null;
	}
	
	@Test
	public void recuperarUsuario() {
		final Usuario usuario = new Usuario();
		
		context.checking(new Expectations(){{
			oneOf(em).find(Usuario.class, 20L);
			will(returnValue(usuario));
		}});
		
		service.recuperarUsuario(new Long(20));
		
		context.assertIsSatisfied();
	}
	
	@Test
	public void gravarUsuario() throws CadastroException {
		final Usuario usuario = context.mock(Usuario.class);
		
		context.checking(new Expectations() {{
			ignoring(usuario).setDataCadastro(with(any(Date.class)));
			ignoring(usuario).setFlAtivo(with(any(Boolean.class)));
			
			oneOf(em).persist(usuario);
			oneOf(em).close();
		}});

		service.gravarUsuario(usuario);
		context.assertIsSatisfied();
		
	}
}

Adolfo Eloy,

A utilização de objetos mock (JMock, EasyMock, Mockito, etc) é para você testar apenas o que o seu método faz independente de outros recursos externos (seja outras classes, banco de dados, arquivo xml, txt, etc). A idéia de utilizar o mock é fazer com que você conheça o resultado desses recursos externos. Por exemplo: você pode supor que se você chamar o findById(1) retorna o objeto que você espera e assim você faz os testes unitários dentro do método atual. Caso o resultado dos métodos externos sejam desconhecidos ou varaidos, seria impossível você montar os testes unitários no método atual. Um findById(1) poderia uma hora retornar um valor e na outra hora retornar outro valor…

Talvez o sentimento que você tenha é que o seu método seja tão simples que não precise de teste unitário. Mas a minha experiência diz: sim, todos os métodos precisam de testes unitários e a própria literatura diz: tudo que pode quebrar deve ser testado unitariamente.

Testar unitariamente um método não garante que TODA A FUNCIONALIDADE irá funcionar. Você deve ter testes unitários do início ao fim validando, de preferencia, todos os cenários possíveis. Assim, além de você testar o método gravarUsuario() (sua maior dúvida), você deve testar também os métodos que chamam ele, testar se realmente todos os parâmetros estão sendo preenchidos e assim por diante.

Para finalizar, os testes unitários além de ajudar a testar o sistema e garantir uma melhor qualidade, eles são essenciais para você descobrir problemas de modelagem. Classes com alto nivel de acoplamento são dificeis de testar e fazendo testes unitários, você irá identificar isso antes mesmo de construir o seu sistema.

Espero que tenha ficado claro essa dúvida

Olá Jair.

Em primeiro lugar obrigado pela disposição em ajudar.
Gostaria de estender um pouco mais a discussão para tentar fundamentar melhor o conceito dos testes unitários pra mim ok?

Na maioria dos testes que criei hoje com o JMock acabei testando mais o comportamento das funcionalidades ao invés do estado das mesmas. Existe algum padrão para quando testar o estado e quando testar o comportamento? Ou podemos testar os dois ao mesmo tempo sem problemas?

Esta observação citada, realmente pude perceber hoje quando iniciei a construção de um serviço que gravava uma Entity “A” de modo que um dos atributos desta entidade precisava ser recuperada do banco de dados em uma Entity “B” antes de persistir.
O meu método inicial fazia um find para recuperar a Entity B, atribuia a entity B em uma propriedade da entity A [ entityA.setB(entityB) ].
Devido a utilização de muitos recursos da JPA, quando fui criar o teste (depois de implementar o serviço), tive uma grande dificuldade em implementar o teste. Assim que terminei de implementar o teste, o mesmo ficou muito grande, confuso e parecia até que eu tinha implementado um novo serviço dentro do teste. Diante deste cenário, optei por quebrar o serviço de gravar a Entity A em 2 partes:

  • um serviço que recuperava a entity B (separada do contexto da gravação de A).
  • e o serviço bem mais legível para gravação da entity A, de modo que para recuperar B bastava chamar o serviço definido para recuperar B.
    Assim o meu teste pode ficar bem menor e consequentemente menos complexo.

Obrigado

Então Adolfo,

Eu não separo o teste do estado e teste de comportamento, pelo contrário, nos meus testes eu crio todos os cenários possíveis daquela funcionalidade. O que é fato é que alguns testes/cenários são mais dificeis de testarem do que outros. Nesse caso, após criar os primeiros testes, é interessante fazer refactoring na própria classe de teste para a criação de novos cenários serem mais fáceis de implementar.