Falae pessoal, pro meu primeiro post aqui no blog queria trocar uma idéia com vocês sobre os famosos mocking frameworks.

Não sabe o que é mocking framework? Sem problema! Antes de continuar dá uma olhada aqui e aqui.

A minha proposta é mostrar um lado que não é muito falado em posts e livros sobre o assunto; Vejo frequentemente uma prática comum entre os adeptos de testes: a primeira coisa que se faz ao começar a testar é “instalar” o mocking framework. Isso me preocupa um pouco e vou tentar explicar o pq.

Qual o problema com mocking frameworks? Em uma palavra: nenhum! Mas… com grandes poderes vêm grandes responsabilidades….

Lembro que da primeira vez que ouvi o Tio Bob, falando que não usava mocking frameworks no EP: 23 do Clean Coders achei um tremendo absurdo!!! Como assim??? ELE não usa mocking framework?!?!?!

Pois é… não usa… por um simples motivo: ele não precisa.

E ele tem um bom argumento pra isso: Para os testes que ele faz (e acredite, ele testa quase tudo) ele não precisa de todo o poder que um mocking framework provê e diga-se de passagem, é um tremendo trabuco que você tem em mãos.

Fora isso, se formos pensar num nível um pouco mais alto, por ex na arquitetura da nossa aplicação, a coisa fica um pouco mais complicada.

Imagine que estejamos trabalhando com o padrão Service Layer, queremos um sistema desacoplado e separamos a lógica de negócio em pequenas classes de serviço, SHOW!!!

Aprendemos com a tia Teteca que o melhor dos mundos é usar a famosa injeção de dependência, e ela também falava que o bunito era declarar as dependências via construtor; LOGO fizemos isso tudo e temos nossas classes de UI declarando dependências das classes de serviço e por sua vez as classes de serviço possivelmente dependendo de classes de infra estrutura (repositórios, etc..).

public class AuthenticationController
    {
        private readonly IUserService _userService;

        public AuthenticationController(IUserService userService)
        {
            _userService = userService;
        }

        public bool Autenticar(Usuario usuario)
        {
            return _userService.Autenticar(usuario);
        }
    }

Beleza! Vamos testar essa budega, criamos uma classe de teste para a classe de UI e para satisfazer o compilador, instanciamos o serviço que ela depende usando o mocking framework. Para os testes estou usando o XUnit e Moq.

public class AuthenticationServiceTests
    {
        [Fact]
        public void Usuario_Existente()
        {
            var userService = new Mock<IUserService>();

            var autenticador = new AuthenticationController(userService.Object);
            var usuario = new Usuario {Nome = "Usuario", Senha = "123"};

            var response = autenticador.Autenticar(usuario);

            Assert.True(response);
        }

Então, o compilador está feliz mas o framework não é tão inteligente assim pra saber o que o serviço precisa fazer, então eu tenho que configurar o framework pra fazer com que a classe se “comporte” do jeito que eu espero. Geralmente isso é feito no método de setup do teste.

public class AuthenticationServiceTests
    {
        private readonly Mock<IUserService> _userService = new Mock<IUserService>();
        private readonly Usuario _usuario = new Usuario {Nome = "Usuario", Senha = "123"};

        [Fact]
        public void Usuario_Existente()
        {
            //Setup
            _userService.Setup(x => x.Autenticar(It.IsAny<Usuario>())).Returns(true);

            var autenticador = new AuthenticationController(_userService.Object);

            //Action
            var response = autenticador.Autenticar(_usuario);

            //Assert
            Assert.True(response);
        }

        [Fact]
        public void Usuario_Inexistente()
        {
            //Setup
            _userService.Setup(x => x.Autenticar(It.IsAny<Usuario>())).Returns(false);

            var autenticador = new AuthenticationController(_userService.Object);

            //Action
            var response = autenticador.Autenticar(_usuario);

            //Assert
            Assert.False(response);
        }
    }

O teste passa e continuamos o desenvolvimento, muito provavelmente a nossa classe da view vai precisar de outros serviços, pra isso seguimos os mesmos passos: criamos o serviço, declaramos como dependência e criamos o mock nos testes.

Mas… nada impede do serviço depender de outros serviços também…

Nesse caso, criamos uma “teia” de dependências que será tão grande e complexa quanto a sua aplicação. Mas… você não vê. Porquê?

O mocking framework te “esconde” isso porque ele vai criar uma classe em runtime com uma implementação “stub” para os métodos e propriedades dessa classe; fará o mesmo para as dependências desta classe e só mudará esse comportamento caso você diga o contrário, fazendo o setup da classe à ser mockada.

Como provavelmente quem cria as classes em runtime é um IoC container você não sente o quão penoso é a criação de todos esses objetos e suas dependências.

MAS…… hoje em dia os IoC’s são tão poderosos, rápidos, escalafobéticos e… o seu sistema continua testável, então… qual o problema????

Bem, sabe aquele papo de sistema desacoplado… não tá né…..

O seu sistema pode estar pedindo um refactor, quem sabe ele está sofrendo de classes com responsabilidades demais? ou talvez as interfaces estejam muito “gordas”. Já vi sistemas com um alto code coverage mas também tão acoplado que existiam dependências cíclicas entre as classes.

Não tenho nada contra mocking frameworks, eles são ótimos diga-se de passagem, principalmente pra testar classes que não são suas. Para todas as outras, porquê não uma abordagem mais simples?

Você pode dizer: “bem, não é tão simples quando você tem que criar classes stubs para cada teste unitário!”. Eu concordo e faço outra pergunta: realmente precisa da relação 1 x 1?

Como toda decisão de engenharia esta tem seus tradeoffs também. Vamos tentar um rápido comparativo entre as duas abordagens:

Mocking Sem mock
Aprendizado Depende do framework Princípios básicos de OO
Resultado no código de teste Setup mais complexo Setup mais simples
Manutenção Somente no código de Setup do teste Manutenção de N classes “stub”
Na alteração de assinatura de métodos Métodos de setup terão que ser alterados Classes “stubs” terão que ser alteradas
Na alteração de dependências dos serviços mockados Nenhuma alteração no código de setup As novas dependências terão que ser instanciadas no setup da classe de teste
Reutilização É possível com criação de classes extras É possível por default

Esse é um assunto polêmico e muito extenso para um post só. Por isso no próximo post falarei dos tipos de mock e como é possível realizar testes unitários com o mínimo de classes possível, procurando manter o código “suporte” dos testes o mais “blindado” possível sobre mudanças no código de produção, utilizando somente técnicas OO e boas práticas.

Mas…. o que você achou do post? Comenta aí embaixo ou me manda um email, vai ser um prazer enorme ouvir outros pontos de vista sobre o assunto!

Abraços e até a próxima

Leonardo Vieira

Graduando em Análise e Desenvolvimento de sistemas pelo Instituto Infnet, é evangelista de boas práticas, clean code e tdd.

Facebook Twitter LinkedIn 

Comentários

comentarios