.NET - Apresentando e usando o Rhino Mocks (simulação e testes)
Esse artigo vou apresentar os conceitos básicos sobre o Rhino Mocks e como utilizar alguns de seus recursos. |
O Rhino Mocks pode ser visto como um framework dinâmico para simulação de
objetos para plataforma .Net. Seu objetivo é facilitar os testes, permitindo ao
desenvolvedor criar implementações de simulação de objetos personalizados e
verificar as interações usando testes de unidade.
Existe na língua portuguesa o verbo mocar que significa enganar, atraiçoar ou ainda esconder (popular), mas na área de software mocar objetos ou Mock Objects significa objetos que imitam objetos reais para realização de testes de software.
Assim os mock objetos seria objetos criados usando frameworks com o objetivo de simular objetos reais e facilitar a sua criação quando da realização de testes.
O Rhino Mocks é um destes frameworks muito popular para plataforma .NET
Mas porque alguém iria precisar mocar objetos usando um framework ?
Se você quiser praticar o Test-Driven Development (TDD), então não tem como evitar o uso de um Framework para mocar objetos. Você vai precisa usar estes frameworks para realizar uma simulação e criar testes de unidade eficazes.
Test Driven Development (TDD)
ou em português Desenvolvimento guiado por testes é uma técnica de
desenvolvimento de software que baseia em um ciclo curto de repetições:
Primeiramente o desenvolvedor escreve um caso de teste automatizado que define uma melhoria desejada ou uma nova funcionalidade. Então, é produzido código que possa ser validado pelo teste para posteriormente o código ser refatorado para um código sob padrões aceitáveis. Kent Beck, considerado o criador ou o 'descobridor' da técnica, declarou em 2003 que TDD encoraja designs de código simples e inspira confiança1.
O
Desenvolvimento dirigido por testes é relacionado a conceitos de
programação de Extreme Programming, iniciado em 1999,2 mas recentemente
tem-se criado maior interesse pela mesma em função de seus próprios
ideais.3 Através de TDD, programadores podem aplicar o conceito de
melhorar e depurar código legado desenvolvido a partir de técnicas
antigas. |
Os testes unitários permite testar uma única unidade de código de forma isolada. Normalmente, uma unidade de código corresponde a um único método em sua aplicação. Portanto, um teste de unidade deve permitir-lhe testar apenas o código dentro de um método particular e nada mais.
Um teste unitário, para fins de Test-Driven Development, possui requisitos adicionais e um deles é que ele deve ser executado muito rapidamente e não necessitar de qualquer configuração do aplicativo.
Ao praticar o Test-Driven Development, você executa todos os testes sempre que você faz uma alteração de código. Muito provavelmente, você vai executar esses testes centenas de vezes durante a criação da aplicação. Se os seus testes de unidade forem lentos ou se eles derem trabalho para configurar o ambiente de desenvolvimento , esses testes não serão nada práticos.
Muito prazer SUT !
Antes de continuar tenho que apresentar a você o SUT.
Mas o que é SUT ?
A sigla SUT significa System Under Test (sistema em teste) e refere-se ao sistema o qual estamos testando.
Verificando estado e comportamento
Um Framework para mocar objetos como o Rhino Mocks pode ser usado para executar dois tipos diferentes de testes de unidade :
Verificação de estado;
Verificação de comportamento.(Esta distinção costumava ser chamado verificação de estado e o teste baseado em interação)
Ao realizar a verificação de estado, a
declaração final em seu teste de unidade é tipicamente um comando assert
que afirma que alguma condição é verdadeira.
Na verificação de comportamento (um teste de interação), por outro
lado, você está interessado em verificar como um conjunto de objetos se
comportam e interagem de uma forma particular.
Stubs não são Mocks ( "Mocks Aren’t Stubs" - Martin Fowler )
Podemos ainda usar o Rhino Mocks
para criar
stubs que são objetos para auxiliar no testes de ambientes.
Há várias razões que você pode criar um stub. Por exemplo, objeto real
pode acessar um recurso externo, tal como um banco de dados ou o sistema de
arquivo fazendo com que o objeto atual se torne muito lento para ser utilizado
em um teste unitário.
O stub permite que você execute um teste em uma quantidade razoável de tempo, sem necessidade de qualquer configuração.
Então temos que :
Mocks : São objetos pré-programados com informações que formam uma especificação das chamadas que esperam receber;
Stubs : Providenciam respostas pré-configuradas para as chamadas feitas durante os testes, em geral não respondem a nada que não esteja programado para o teste.
Ou seja os Stubs são usados para representar e testar o estado de um objeto e os Mocks são usados para testar as suas interações.
Usando o Rhino Mocks
Vamos agora criar um projeto usando o Visual Studio Community 2013 usando a linguagem Visual C# e o template Console Application com o nome Usando_RhinoMocks :
A seguir vamos incluir uma classe chamada ProdutoBase neste projeto com o seguinte código
namespace Usando_RhinoMocks
{
public abstract class ProdutoBase
{
public abstract string Nome { get; set; }
public abstract decimal Preco { get; set; }
public abstract void Salvar();
}
}
|
Vamos partir da classe ProdutoBase e supor que
pretendemos em algum momento no projeto criar determinados tipos de produtos que
derivam dessa classe base - por exemplo, ProdutoEletronico, ProdutoEsportivo,
etc., sem que até agora tenhamos criado quaisquer dessas classes concretas.
Como podemos fazer isso ??
Usando o Rhino Mocks, claro...
Usando os recursos do Rhinos Mocks podemos 'fingir' que já temos as classes
criadas , ou seja, podemos mocar as classes.
Para fazer isso vamos então criar o projeto onde iremos realizar os testes e
usar os recursos do Rhino Mocks.
Criando o projeto de testes e usando o Rhino Mocks
No menu FILE clique em Add -> New Project;
Selecione o template Visual C# -> Test e o template Unit Test Project e informe o nome RhinoMocks_Teste;
A seguir vamos incluir uma referência ao projeto Usando_RhinoMocks neste projeto.
Clique com o botão direito sobre o projeto RhinoMocks_Teste e a seguir clique em Add -> Reference;
Clique na guia Projects e marque o projeto
Usando_RhinoMocks;
Vamos incluir uma referência ao Rhino Mocks neste projeto.
Clique com o botão direito do mouse sobre o projeto RhiMocks_Teste e a seguir clique em Manage Nuget Packages;
Digite RhinoMocks na caixa Search Online e a seguir selecione o pacote RhinoMocks e clique no botão Install;
Podemos também inclua uma referência ao Rhino Mocks usando o Package Manager Console no menu TOOLS e digitando o comando descrito a seguir no console:
Nuget Package
PM> Install-Package RhinoMocks -Version 3.6.1
|
Para realizar o teste vamos alterar o nome da classe de teste criada por padrão para RhinoMocks_Teste e incluir o seguinte código nesta classe:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
using Usando_RhinoMocks;
namespace RhinoMocks_Teste
{
[TestClass]
public class RhinoMock_Teste
{
[TestMethod]
public void TesteStubProdutoAbstract()
{
// Configura um stub produto
ProdutoBase produto = MockRepository.GenerateStub<ProdutoBase>();
produto.Nome = "Teclado";
produto.Preco = 34.50m;
//Teste
Assert.AreEqual(34.50m, produto.Preco);
}
}
}
|
Neste código estamos chamando o método estático MockRepository.GenerateStub<ProdutoBase>() para gerar um stub para a classe abstrata ProdutoBase.
Depois de gerar o stub podemos tratá-lo como uma classe normal e definir e ler suas propriedades.
A instrução Assert deverá retornar o valor True visto que o preço atribuído à propriedade Preco é igual a valor verificado: 34.50.
Podemos também usar o Rhino Mocks para gerar valores
de retorno falsos a partir de classes e interfaces para as quais já tenhamos
gerado stubs.
Imagine que você precisa testar um método que se baseia em dados de banco de
dados. No entanto, você não quer acessar o banco de dados quando você executa o
teste de unidade, pois o acesso a banco de dados seria muito lento.
Neste caso, você pode criar um método de stub que sempre retorna um conjunto de valores definidos.
Vamos supor que temos uma classe chamada IProdutoRepositorio que representa a nossa camada de acesso a dados e que usa a interface IProduto. Vamos definir também uma classe Produto que implementa a interface IProduto.
Vamos então criar essas interfaces no projeto Usando_RhinoMocks, o mesmo onde temos a classe ProdutoBase, com o seguinte código:
1- IProduto
namespace Usando_RhinoMocks
{
public interface IProduto
{
string Nome { get; set; }
decimal Preco { get; set; }
}
}
|
2- Produto
using System;
namespace Usando_RhinoMocks
{
public class Produto : IProduto
{
public string Nome
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
public decimal Preco
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
}
}
|
3- IProdutoRepositorio
using System.Collections.Generic;
namespace Usando_RhinoMocks
{
public interface IProdutoRepositorio
{
IProduto Get(int ProdutoId);
IEnumerable<IProduto> Seleciona();
bool Salvar(IProduto produto);
}
}
|
No código acima temos um método para
recuperar um conjunto de registros de produtos, um método para recuperar um
determinado registro de produtos, e um método para salvar um produto.
Vamos criar um teste unitário usando Rhino Mocks criando um stub para a interface IProdutoRepositorio e definindo a configuração do método Seleciona() do stub de forma que ele sempre retorna um conjunto de produtos mocados ou falsos.
Para isso vamos definir no projeto RhinoMocks_Teste, na classe RhinoMock_Teste o seguinte código :
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
using System.Collections.Generic;
using Usando_RhinoMocks;
using System.Linq;
namespace RhinoMocks_Teste
{
[TestClass]
public class RhinoMock_Teste
{
private IEnumerable<IProduto> _fakeProdutos = new List<IProduto>
{
new Produto {Nome = "Carne", Preco = 9.50m},
new Produto {Nome = "Leite", Preco = 2.30m},
new Produto {Nome = "Ovos", Preco = 3.50m}
};
[TestMethod]
public void TesteStubProdutoInterface()
{
MockRepository mocks = new MockRepository();
IProdutoRepositorio produtos = mocks.Stub<IProdutoRepositorio>();
using (mocks.Record())
{
SetupResult.For(produtos.Seleciona()).Return(_fakeProdutos);
}
var resultados = produtos.Seleciona();
Assert.AreEqual(3, resultados.Count());
}
}
}
|
Há três diferenças importantes que você deve observar entre o código acima e o
usado nos exemplos anteriores.
Primeiro, observe que a sub para a interface IProdutoRepositorio não é
criado usando o método GenerateStub<T>().
Quando definir os valores de retorno do método, você deve criar uma instância da
classe MockRepository e chamar o método de instância Stub.
Em segundo lugar, note que classe SetupResult é usada para configurar o
valor de retorno para o método IProdutoRepositorio.Seleciona().
Quando o método Seleciona() for chamado, o conteúdo do campo _fakeProdutos
será devolvido.
Finalmente, observe que a chamada para SetupResult é envolta em uma
declaração que faz referência ao método MockRespository.Record().
A declaração using em conjunto com o
método de Record() são usados para registrar como o stub deve se
comportar quando um método stub particular for chamado. Você pode configurar
vários métodos de stub dentro da única instrução using.
Você pode até mesmo retornar valores diferentes a partir de um método de stub
quando diferentes parâmetros são passados para o método.
Vamos incluir o método TesteStubMultiploRetorno
na classe
RhinoMock_Teste do projeto
RhinoMocks_Teste com o código abaixo:
[TestMethod]
public void TesteStubMultiploRetornos()
{
MockRepository mocks = new MockRepository();
IProdutoRepositorio produtos = mocks.Stub<IProdutoRepositorio>();
using (mocks.Record())
{
SetupResult
.For(produtos.Get(2))
.Return(new Produto { Nome = "Cerveja", Preco = 4.99m });
SetupResult
.For(produtos.Get(12))
.Return(new Produto { Nome = "Carne", Preco = 12.50m });
}
//Teste
IProduto produto1 = produtos.Get(2);
Assert.AreEqual("Cerveja", produto1.Nome);
IProduto produto2 = produtos.Get(12);
Assert.AreEqual("Carne", produto2.Nome);
IProduto produto3 = produtos.Get(13);
Assert.IsNull(produto3);
}
|
Este código retorna diferentes produtos dependendo do código do produto informado ao método ProdutoRepositorio.Get().
Verificando o comportamento com Rhino Mocks
Você pode usar o Rhino Mocks para verificar
se um determinado conjunto de objetos interagem de uma maneira esperada. Por
exemplo, você pode verificar se um método de um determinado objeto foi chamado o
número esperado de vezes com o conjunto esperado de parâmetros. Este tipo de
teste é também chamado de teste com base em interação.
Este tipo de teste é útil quando você não pode verificar o estado de um objeto
depois que você interagir com o objeto. Por exemplo, um objeto pode não ter uma
propriedade pública contra a qual você possa usar um Assert. Portanto,
não há nenhuma maneira direta para validar o estado do objeto depois de executar
o teste de unidade.
Imagine que você precisa criar um componente de log. Toda vez que você realizar
uma ação significativa em seu aplicativo, você quiser registrar a ação em um
arquivo de log no disco. Se o seu componente de logr não tem uma propriedade que
expõe todas as suas entradas de log, não haverá nenhuma maneira simples de
verificar se o log está se comportando conforme o esperado.
No entanto, você pode interceptar solicitações para o componente log e pode
verificar se outros componentes estão interagindo com o componente quando você
espera. A verificação de comportamento é realizar este tipo de teste da
interação entre diferentes componentes.
Ao realizar testes de verificação de comportamento com o Rhino Mocks, seu código de teste é dividido em duas seções. Primeiro, você tem uma seção de código onde você grava as suas expectativas. Em segundo lugar, você tem uma seção de código onde as suas expectativas são testadas contra a realidade.
A título de exemplo vamos incluir no projeto Usando_RhinoMocks as classes Log e Cliente com o código abaixo:
1- Classe Log
namespace Usando_RhinoMocks
{
public class Log
{
public int Id { get; private set; }
public Log()
{}
public virtual void Logar(int id) { throw new Exception("erro"); } } } |
2- Classe Cliente
namespace Usando_RhinoMocks
{
public class Cliente
{
public int Id { get; private set; }
public string Nome { get; set; }
private Log _log;
public Cliente(int Id, Log log)
{
this.Id = Id;
_log = log;
}
public void Salvar()
{
_log.Logar(this.Id);
}
}
}
|
A seguir vamos definir a classe RhinoMocksLogTeste no projeto RhinoMocks_Teste :
[TestClass]
public class RhinoMocksLogTeste
{
[TestMethod]
public void LogTeste()
{
MockRepository mocks = new MockRepository();
Log _log = mocks.CreateMock<Log>();
using (mocks.Record())
{
_log.Logar(27);
}
using (mocks.Playback())
{
Cliente novoCliente = new Cliente(27, _log);
novoCliente.Nome = "Macoratti";
novoCliente.Salvar();
}
}
}
|
Este código é um exemplo de teste unitário para um componente de log que citamos acima.
Na primeira parte do código temos um conjunto de expectativas gravadas. Assim quando o método Cliente.Salvar() for chamado o método _log.Logar() deve ser chamado com o código a ser salvo.
Se você observar o código da classe Log implementada no projeto Usando_RhinoMocks verá que essa classe não foi realmente implementada visto que o método Logar() lança uma exceção e como estamos mocando o método Logar esta exceção nunca será lançada.
Rhino Mocks Opções de uso
O Rhino Mocks suporta três tipos básicos de objetos
fictícios:
1- Strict Mock : A simulação rigorosa exige que você forneça
implementações alternativas para cada método/propriedade que for usada na
simulação. Se quaisquer métodos/propriedades usadas não tiver uma implementação,
uma exceção será lançada.
2- Mock Dinâmico : Com uma simulação dinâmica, quaisquer
métodos/propriedades que forem chamados por seus testes e não tiver uma
implementação, retornará o valor padrão para o tipo do valor de retorno de
dados. Em outras palavras, você vai receber o valor 0 para tipos de números,
falsos para Booleanos e nulos para quaisquer tipos de objetos.
3- Mock parcial : A simulação parcial vai usar a implementação do
objeto subjacente se você não fornecer uma implementação alternativa. Então, se
você só está querendo substituir algumas das funcionalidades (ou propriedades) e
manter o resto, você vai querer usar esta opção. Por exemplo, se você só quer
substituir o método IsLogAtivo() e deixar o resto da classe como está,
você vai querer usar uma simulação parcial para só fornecer uma implementação
alternativa para IsLogAtivo().
Estes são alguns dos muitos recursos que você poderá usar do Rhino Mocks e é apenas uma pequena amostra do que ele pode lhe oferecer para realizar testes unitários com simulações.
Para se aprofundar mais sugiro que você acesse a documentação da ferramenta em : http://www.ayende.com/wiki/Rhino+Mocks+Documentation.ashx
Pegue o projeto completo aqui: Usando_RhinoMocks.zip
Quem
ama a sua vida perdê-la-á, e quem neste mundo odeia a sua vida, guardá-la-á para
a vida eterna.
Se alguém me serve, siga-me, e onde eu estiver, ali estará também o meu servo.
E, se alguém me servir, meu Pai o honrará.
João 12:25,26
Veja os
Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique
e confira !
Quer migrar para o VB .NET ?
Quer aprender C# ??
Quer aprender os conceitos da Programação Orientada a objetos ? Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ? |
Gostou ? Compartilhe no Facebook Compartilhe no Twitter
Referências: