Mocks - como utilizar?
Como já expliquei num artigo anterior (que podes ler AQUI) os Mocks são objetos simulados que imitam o comportamento de objetos reais num ambiente controlado de teste.
São usados para testar partes do software de forma isolada, substituindo os componentes reais com que o sistema em teste interage (como serviços web, bases de dados, ou APIs externas) por componentes simulados.
Os Mocks permitem assim simular diferentes cenários e condições, sem a necessidade de envolver os componentes reais, facilitando então a identificação de problemas e o controlo das variáveis no ambiente de teste.
Como podem ser utilizados?
Os Mocks podem ser utilizados em várias situações durante os testes de software:
Testes Unitários:
substituem dependências externas (como serviços ou bases de dados) para focar no teste de uma única unidade de código.
Exemplo: Testar uma função que processa dados obtidos de uma API sem efectivamente realizar a chamada à API.
Testes de Integração:
simulam interações com componentes de sistemas externos para verificar a integração entre as diferentes partes do sistema.
Exemplo: Testar um módulo que insere registos dados numa base de dados, sem aceder à base de dados verdadeira.
Testes de Sistema e End-to-End (E2E):
permitem simular a interação com sistemas externos que não estão sob o controlo da equipa de desenvolvimento.
Exemplo: Simular respostas de um serviço de pagamento, para testar o fluxo de pagamento numa aplicação de e-commerce.
Por é que foram desenvolvidos?
Os Mocks foram desenvolvidos para resolver vários problemas comuns no desenvolvimento e teste de software, como por exemplo:
Isolamento de Componentes: permitem testar componentes individuais de forma isolada, sem a influência/interação de dependências externas.
Reprodução e Controlo: permitem ter um ambiente de testes controlado e previsível, no qual as respostas dos componentes simulados são conhecidas e consistentes.
Redução de custo e complexidade: eliminam a necessidade de configurar e manter sistemas externos complexos durante os testes.
Teste de cenários de erro: facilitam a simulação de cenários de falha ou comportamentos excepcionais, que podem ser difíceis de reproduzir num ambiente real.
Onde é que são utilizados?
Os Mocks são amplamente utilizados em diferentes níveis de testes:
Desenvolvimento de Software
os programadores usam os mocks para testar funções e métodos durante o desenvolvimento do código, garantindo que funcionam conforme esperado antes de integrar o novo código com outros componentes.
QA e Testes Automáticos
os QAs usam os mocks para criar testes automáticos que validam a funcionalidade do software, sem depender de sistemas externos.
DevOps e CI/CD
os Mocks são utilizados em pipelines de integração contínua e entrega contínua (CI/CD), para executar testes automáticos de forma rápida e confiável.
As melhores práticas para utilização
Isolamento apropriado
devemos utilizar mocks para isolar a unidade de teste em questão do resto do sistema, mas devemos evitar o uso excessivo (e até abusivo) de mocks na fase de testes de integração, onde a interação real entre componentes deve ser testada.
Manter a simplicidade
ao manter os mocks simples e focados nos comportamentos necessários para o teste, evitamos criar mocks excessivamente complexos que podem introduzir problemas (o oposto do que nós queremos).
Veracidade
certifica-te de que os mocks reflectem o comportamento real das dependências, tanto quanto possível, a fim de garantir a validade dos testes. Caso contrário, obterás resultados falsos e que induzirão em erro.
Documentação e manutenção
documenta a finalidade e o comportamento dos mocks utilizados, para facilitar a manutenção e compreensão dos mesmos no futuro.
actualiza os mocks sempre que as interfaces das dependências reais foram alteradas.
Utilização de bibliotecas de mocking
utiliza bibliotecas e frameworks de mocking - como Mockito para Java, unittest.mock para Python, ou Sinon.js para JavaScript - que fornecem ferramentas e funcionalidades robustas para a criação e gestão de mocks.
Teste de múltiplos cenários
cria mocks que possam simular diferentes cenários (incluindo respostas bem-sucedidas, erros e exceções) para garantir uma cobertura de testes abrangente.
Exemplos Práticos de Utilização de Mocks
Exemplo 1: Teste de Unidade de um Serviço que Depende de uma API Externa
Imagina que estás a desenvolver um serviço que depende de uma API externa para obter dados sobre a previsão do tempo. Testar este serviço diretamente pode ser complicado devido à incerteza sobre a disponibilidade e a resposta da API externa. Portanto, podes usar um mock para simular a API.
Implementação em JavaScript com Jest:
// weatherService.js class WeatherService { constructor(apiClient) { this.apiClient = apiClient; } async getWeather(city) { const response = await this.apiClient.get(`/weather?city=${city}`); return response.data; } } module.exports = WeatherService; // weatherService.test.js const WeatherService = require('./weatherService'); const axios = require('axios'); jest.mock('axios'); describe('WeatherService', () => { it('should return weather data for a given city', async () => { const mockResponse = { data: { temp: 20, description: 'Sunny' } }; axios.get.mockResolvedValue(mockResponse); const weatherService = new WeatherService(axios); const weather = await weatherService.getWeather('Lisbon'); expect(weather).toEqual(mockResponse.data); }); });
Exemplo 2: Teste de Integração com um Mock de Banco de Dados
Para testar um serviço que interage com uma base de dados, podes usar mocks para simular as operações da base de dados, garantindo que os teus testes não dependem do estado real do Banco.
Implementação em Python com unittest e unittest.mock:
# user_service.py class UserService: def __init__(self, db_client): self.db_client = db_client def get_user(self, user_id): return self.db_client.fetch_user(user_id) # test_user_service.py import unittest from unittest.mock import Mock from user_service import UserService class TestUserService(unittest.TestCase): def test_get_user(self): mock_db_client = Mock() mock_db_client.fetch_user.return_value = {'id': 1, 'name': 'Alice'} user_service = UserService(mock_db_client) user = user_service.get_user(1) self.assertEqual(user, {'id': 1, 'name': 'Alice'}) if __name__ == '__main__': unittest.main()
Dicas Práticas para Utilizar Mocks
Escolher a ferramenta certa
Usar ferramentas de mock ,que se integre bem com a sua linguagem de programação e framework de testes, é muito importante. Exemplos de ferramentas são: Mockito para Java, Jest para JavaScript, e unittest.mock para Python.
Não abuses dos Mocks
Utilizar mocks para isolar dependências externas, mas evitar “mockar” tudo. Mocks em excesso podem resultar em que os resultados dos testes não reflitam a realidade do sistema.
Mantém os Mocks simples
Mantém os teus mocks simples e focados em simular comportamentos específicos.
Evita a utilização de lógica complexa dentro dos mocks.
Verifica as interações importantes
Além de verificar os retornos dos mocks, certifica-te de que as interações esperadas com os mocks aconteceram mesmo. Para isso, podes usar métodos como verify no Mockito ou assert_called_with no unittest.mock.
Recursos Adicionais
Se quiseres aprofundar o conhecimento sobre mocks e outras práticas de teste, aqui estão alguns recursos:
Livros:
"The Art of Unit Testing" por Roy Osherove
"Growing Object-Oriented Software, Guided by Tests" por Steve Freeman e Nat Pryce
Cursos Online:
Coursera: "Software Testing and Automation"
Udemy: "Automated Software Testing with Python"