Objeto mock

objetos virtuais que simulam objetos reais em ambiente controlado

Objetos mock, objetos simulados ou simplesmente mock (do inglês mock object) em desenvolvimento de software são objetos que simulam o comportamento de objetos reais de forma controlada. São normalmente criados para testar o comportamento de outros objetos. Em outras palavras, os objetos mock são objetos “falsos” que simulam o comportamento de uma classe ou objeto “real” para que possamos focar o teste na unidade a ser testada.

Razões para usar

editar

Em teste unitário, pode simular o comportamento de objetos reais complexos e são, portanto, muito úteis quando os objetos reais são difíceis de criar ou impossíveis de serem incorporados no teste de unidade. Por exemplo, em um teste para um objeto que faz uma chamada remota é necessário que do outro lado exista um previsão para a chamada. Neste caso, em vez de usar o objeto real que faz a chamada, usa-se um mock object que simula o comportamento do objeto real. Se um objeto tem alguma das características a seguir, é provável que mock objects possam ser utilizados em seu lugar:

  • gera resultados não determinísticos (e.g. a hora ou temperatura atual);
  • tem estados que são difíceis de criar ou reproduzir (e.g. erro de comunicação da rede);
  • é lento (e.g. um banco de dados completo que precisa ser inicializado antes do teste);
  • ainda não existe ou pode ter comportamento alterado;
  • teriam que adicionar informações e métodos exclusivamente para os testes (e não para sua função real).

Por exemplo, um programa de despertador que faz uma campainha tocar em um certo momento necessita obter a hora atual. Para testar este comportamento, o teste de unidade deve esperar até que a hora programada chegue, para testar a campainha de forma correta. Se um mock object for usado no lugar do objeto real, então ele pode ser programada para simular a hora de programação do despertador. Assim o código do despertador pode ser testado corretamente.

Exemplo

editar

Suponha que um banco de dados de produção precisa ser testado, este SGBD já tem uma boa quantia de registros e uma busca no mesmo é lenta.

para tal teste, é criado uma classe MockOrderRepositoryService que simula o serviço do banco e uma classe, OrderRepository, que é responsável pela injeção de dependência:

Código em C#

public class Order
{
    public int Id { get; set; }

    public string Client { get; set; }

    public double Value { get; set; }
}

public interface IOrderRepositoryService
{
    void Add(Order order);
    Order Find(int id);
}

Classe que simula o SGBD

public class MockOrderRepositoryService : IOrderRepositoryService
{
    private List<Order> orders;

    public MockOrderRepositoryService()
    {
        this.orders = new List<Order>();

        // para os testes é necessário que 
        // exista algum dado no banco
        this.orders.Add(new Order() { Id = 1, Value = 140.50, Client = "Lucas" });
        this.orders.Add(new Order() { Id = 2, Value = 95.40, Client = "Caroline" });
        this.orders.Add(new Order() { Id = 3, Value = 39.99, Client = "Bruna" });
    }

    public void Add(Order order)
    {
        this.orders.Add(order);
    }

    public Order Find(int id)
    {
        if (id == 1)
        {
            // simulando uma falha do SGBD ao pesquisar pelo pedido com ID = 1
            return null;
        }
        return this.orders
            .Where(x => x.Id == id)
            .FirstOrDefault();
    }
}

public class OrderRepository
{
    public IOrderRepositoryService Service { get; set; }

    public OrderRepository(IOrderRepositoryService service)
    {
        this.Service = service;
    }

    public void AddOrder(Order order)
    {
        this.Service.Add(order);
    }

    public Order FindOrder(int id)
    {
        return this.Service.Find(id);
    }
}

A seguinte utilização de testes unitários[1] sobre o serviço do banco não irá utilizar o banco de dados de produção (lento), mas sim o serviço simulado:

Código em C#

[TestClass]
public class OrderTest
{
    [TestMethod]
    public void FindOrders()
    {
        // utilizando um repositório (mock) 
        // sem precisar utilizar o banco de dados de produção
        OrderRepository repository = new OrderRepository(new MockOrderRepositoryService());

        // isto, sem o mock, realizaria uma busca no banco
        Order order = repository.FindOrder(2);

        // testes unitários
        Assert.IsNotNull(order, "o pedido 2 não foi localizado");
        Assert.AreEqual(order.Id, 2, message: "O pedido 2 não está com as propriedades corretas (Id)");
        Assert.AreNotEqual(order.Value, 0d, message: "O pedido tem preço igual à 0");

        // sabemos que o pedido 1 existe, porém o mock simula uma falha
        order = repository.FindOrder(1);
        Assert.IsNotNull(order, "O pedido 1 não foi localizado");
    }
}

Limitações

editar

O uso de objetos mock pode casar os testes unitários com a atual implementação do código que está sendo testado. Por exemplo, alguns frameworks de objetos mock permitem ao desenvolvedor especificar a ordem e o número de vezes que os métodos num objeto mock são chamados; um sucessivo refactoring do código que está sendo testado, poderia portanto fazer que o teste falhe, mesmo se o método ainda obedece a lógica da implementação anterior.

Isso nega completamente um dos benefícios oferecidos pelos testes unitários. Excesso de uso de objetos mock como parte de uma suíte de testes unitários poderia então resultar num crescimento dramático de modificações que precisam ser feitas nos testes durante a evolução do sistema quando o refactoring é feito. Manutenção incorreta durante a evolução destes testes pode fazer com que certos erros passem despercebidos. Tais erros podem ser notados por testes unitários que usam instâncias de classes reais, os quais não precisam ser feitos frequentemente.

Um objeto mock tem precisamente o modelo do comportamento do objeto que ele está simulando ("mocking"). O comportamento correto pode se tornar difícil de se obter se o objeto que está sendo simulado ("mocked") foi feito por outro desenvolvedor, ou, quando esse não tenha sido escrito ainda. Se o comportamento não for modelado corretamente, então os testes unitários devem registrar que o teste passou mesmo se uma falha ocorresse no tempo de execução, nas mesmas condições que o teste está exercendo e dessa forma, tornando inútil o teste unitário.

Referências

  1. Microsoft. «Testes unitários». Consultado em 29 de dezembro de 2014 

Ligações externas

editar
  Este artigo sobre informática é um esboço. Você pode ajudar a Wikipédia expandindo-o.