.NET

9 mai, 2012

Como testar e gerar mock de tipos ou membros protected e internal

Publicidade

O Rhino Mocks é um framework para geração de mocks, artifício muito utilizado na abordagem de desenvolvimento TDD. Um grande problema é que o Rhino Mocks, ao contrário de frameworks como o Moq, não conseguem “mockear” métodos e propriedades com modificadores de acesso restrito, como protected e internal. Isso força o desenvolvedor a definir como public todos os tipos e membros implementados, o que acarreta em uma exposição indesejada e mas forçada do que não é verdadeiramente público.

O que fazer?

Existem alguns caminhos a se seguir: mudar de framework, implementar duas interfaces para cada classe (uma com a exposição do que realmente se deve expor para quem irá utilizar a classe e outra com todos os métodos e propriedades expostos, para serem utilizados pelos casos de teste), entre outras formas possíveis. Vou apresentar duas dessas formas neste artigo: mock de modificadores internal e protected.

Algumas definições

  • internal 

 O tipo ou membro internal pode ser acessado por qualquer código implementado no mesmo assembly.

  • protected

O tipo ou membro protected pode ser acessado apenas dentro da classe que ele for implementado ou em classes que especialize a classe que tem o mesmo implementado.

Modificador de acesso: internal

Para exemplificar, de uma forma mais didática, o mock destes dois tipos de modificadores, eu criei um cenário bem simples. Vejamos na pratica:

1. Criei um projeto “Dominio”:

2. Implementei duas classes :

  • Pedido:
public class Pedido {    public virtual List<Item&gt; itens{get;set;}    public virtual decimal ValorTotal { get; set; }     public Pedido()    {       this.itens = new List<Item>();    }     public virtual void  AdicionarItem(Item item)    {       CalcularTotalDoPedido(item);       this.itens.Add(item);    }     Internal virtual void CalcularTotalDoPedido(Item item)    {       throw new NotImplementedException()    } }
  • Itens:
public class Item {     public virtual Pedido Pedido { get; set; }      public Item()     {      }  }

O código visa incluir um item no pedido e também calcular o seu total. Perceba que foi implementado o método AdicionarItem e que ele é público, já o método CalcularTotalDoPedido é internal e não está implementado.

3. Criei um projeto de testes “Dominio.Testes”:

4. Criei uma classe de testes, “Pedido.Testes”:

Note que não tenho acesso ao método internal CalcularTotalDoPedido. Normalmente, os casos de teste estão implementados em um outro assembly. Sendo assim, não consigo gerar o mock de alguém que seja internals, porém existe uma brecha para se fazer isso sem muito esforço.

É preciso informar ao assembly que todos os internals serão visíveis para um assembly específico. Existe um arquivo dentro de properties do projeto, chamado AssemblyInfo.cs, conforme a figura:

5. Inclua no final do arquivo o seguinte trecho de código:

//Para visibilidade dentro do assembly de testes //Perceba que o assembly é o assembly de testes [assembly: InternalsVisibleTo("Dominio.Testes")]

Se acessarmos o caso de teste implementado, veremos que agora nós temos acesso ao método internal CalcularTotalDoPedido, e sendo assim, podemos fazer o mock do mesmo:

Agora conseguimos mockear e até testar, se for o caso, um método de modificador internal.

Modificador de acesso: protected

Antes de mais nada, preciso explicar o que percebi e que me possibilita mockear ou até mesmo testar algo protegido.

Sabemos por definição que o tipo ou membro protected pode ser acessado somente dentro da classe que ele for implementado ou em classes que especializão a classe que tem o mesmo implementado.

Logo, se “Classe B” especializa a “Classe A”, então, a “Classe B” tem acesso na sua implementação a tipos ou membros da “Classe A”. O natural é que ambas as instâncias de B e A não exponham o método protegido, como vemos a seguir:

No entanto, se a “Classe B” instancia ela mesma, então, temos acesso a todo conteúdo protected da “Clase A” na instância da “Classe B”, ficando assim:

Caso a classe C especialize a classe A:

Obteremos o mesmo resultado quando tentamos instanciar a “Classe B” e acessar métodos protected:

Mas se tentarmos instanciar a “Classe C”, nela mesma teremos:

Com isso, conclui-se que, dado uma classe X  que tenha tipos e membros protected e instancie a si mesma em um objeto x, o objeto x irá expor todos os tipos ou membros. Isso nos possibilita gerar um mock e testar.

Vamos aproveitar o mesmo cenário, porém trabalhando com o modificador protected.

O método da classe Pedido CalcularTotalDoPedido agora será protected, o que piorou a história, pois preciso fazer um mock dele e não tenho acesso.

Vamos recordar como funciona o modificador protected:

Visto que uma classe especialista (herança) da classe Pedido tem acesso aos tipos ou métodos protected, temos um caminho para fazermos o teste de Pedidos:

A classe PedidoTestes irá especializar Pedido. Ok, mas e daí? Perceba que isso possibilita testar a classe Pedidos indiretamente. O código vai ficar assim:

Veja que ao invés de mockear o pedido, tendo ele como alvo direto, eu faço o mock de PedidoTestes:

var pedido = repositorioMock.PartialMock<PedidoTestes>();

E em seguida eu gero uma expectativa para CalcularTotalDoPedido que é protegida na implementação real.

pedido.Expect(p => p.CalcularTotalDoPedido(null)).IgnoreArguments();

Rodando o teste:

Acessando um serviço que consumiria a classe Pedido, vemos que o método Calcular CalcularTotalDoPedido não é visível.

Vimos a possibilidade de gerar mock de tipos ou membros internal e protected apenas com os recursos de orientação a objetos e do .Net. Sendo assim, você pode continuar usando o framework Rhino Mocks ou qualquer outro que você já usa sem poluir sua modelagem com um emaranhado de coisas públicas sem a real necessidade.

Até mais!