Test Doubles Swift

Ou o isolamento de testes unitários

Matheus de Vasconcelos
Feb 11 · 8 min read

Se você é um desenvolvedor ou desenvolvedora e acompanha o Blog da Concrete há algum tempo já sabe que os testes são parte essencial do desenvolvimento de produtos, certo? Neste post, vamos falar um pouco mais sobre os testes unitários e como garantir uma das suas características principais: o isolamento.

Mas, antes de tudo: o que são testes unitários?

O teste unitário é um código que testa a menor parte testável de um sistema, a chamada unit, com o objetivo de ajudar a detectar falhas durante a fase de desenvolvimento e evitar problemas nas fases de testes de integração, além de servir como documentação para seu código e garantir que mudanças e adições não alterem o comportamento esperado do sistema já desenvolvido. Estes testes, quando agrupados dentro do contexto que estão se propondo a testar, são chamados de “suíte”.

Características de um teste unitário

De modo geral, uma suíte de testes deve seguir as seguintes características para ser considerada de boa qualidade:

Para garantir o isolamento

Das características de um teste unitário, uma das mais importantes e desafiadoras no desenvolvimento é o isolamento. Para que isso não se torne uma dor de cabeça durante o desenvolvimento, temos algumasboas práticas e padrões que podem ajudar a deixar o seu sistema mais facilmente isolável.

A utilização de injeção de dependência, por exemplo, é uma das principais práticas, uma vez que, com ela, os objetos do sistema apenas conhecem o objeto auxiliar que precisam para executar uma função, mas não necessariamente sabem como criar ele. Assim, é possível mudar o funcionamento desse objeto auxiliar para facilitar o teste sem que o objeto em testes seja afetado. Além disso, é interessante manter a coesão alta e acoplamento baixo dos objetos.

Ambientes caóticos

Até agora, falamos sobre o que seria o ideal em desenvolvimento de testes. Entretanto, nem todo projeto que existe está nesse estado. A deficiência desses projetos acaba gerando um grande desafio durante o desenvolvimento de testes, pois para garantir o isolamento é necessário utilizar lógicas complexas ou fazer o teste do objeto, o que em alguns casos é inviável em termos de tempo.

E aí… chegamos ao objetivo deste post! É possível utilizar Test Doubles para realizar um teste de maneira isolada e sem precisar de grandes refatorações de códigos legado ou até mesmo lógicas muito complexas.

Test Doubles

O termo refere-se a todo objeto que se passa por outro, para que — em um contexto de teste — seja possível testar objetos garantindo o isolamento completo deles. Este termo é descrito no livro xunit test patterns: refactoring test code (Gerard Meszaros).

Os Test Doubles descritos no livro são cinco, e provavelmente todo desenvolvedor já usou algum deles em algum teste. Antes de explicar cada um deles, porém, é importante conhecer os seguintes conceitos de teste:

Além dos tipos de verificação:

Exemplo de um fluxo de State verification, no qual o teste validaria o estado B
No exemplo acima um teste validaria se X, Y e Z foram chamados e se foram chamados na ordem correta, por exemplo

Agora sim… Os Doubles

Dummy

Doubles do tipo Dummy são objetos que não são usados, geralmente parâmetros desnecessários para chamadas de funções (Navigation controller para fazer algum teste que depende de ter um controller em uma navigation na Stack).

Código de exemplo

Fake

Os Fakes são objetos que têm uma implementação para facilitar o teste, mas não devem estar em produção. Seu comportamento costuma ser estático, ou seja, sempre retorna a mesma coisa quando solicitado.

Código de exemplo

Stub

Uma Stub provê uma resposta esperada quando chamada e geralmente tem variáveis que vão ser o retorno de funções. Essas variáveis podem ser alteradas durante o teste, fazendo com que o objeto tenha um funcionamento controlado.

Diferente do Fake, uma Stub pode mudar seu comportamento, ou seja, é dinâmico, enquanto um Fake tem sempre o mesmo comportamento.

Código de exemplo

Spy

O Spy, como o próprio nome diz, funciona como um espião que coleta dados durante o teste, geralmente validando se ou quantas vezes algum método foi chamado. Alguns spies mais complexos podem armazenar o estado de um sistema durante o teste. Esse double pode se utilizar do padrão de projeto Memento para armazenar estados de sistema.

Código de exemplo

Mock

Até o momento todos os doubles apresentados costumam ser utilizados para testes de state verification, facilitando simular os estados esperados ou a realizar a asserção desses estados.

O Mock, no entanto, é um Double para testes de behavior verification, ou seja, aqueles em que o estado do sistema não é necessariamente importante, mas sim o fluxo dentro do objeto. Um Mock costuma utilizar herança para que seja possível manter o comportamento do SUT e ainda assim adicionar estruturas de controle, validando, por exemplo, a ordem em que métodos são chamados e se de fato eles são.

Código de exemplo


Problemas com isolamento

De modo geral, as principais fontes de quebra de isolamento costumam ser chamadas de APIs, de Analytics e uso de variáveis compartilhadas.
A seguir vamos falar de algumas possíveis situações e soluções desses problemas.

Chamada de APIs

Situação 1

No exemplo acima é inviável testar o objeto, uma vez que ele utiliza um URLSession para se comunicar com uma API e fere, assim, o isolamento do teste.

Solução
Para resolver este problemas podemos utilizar os Test Doubles e o princípio de injeção de dependência. Em um primeiro passo é possível fazer com que o objeto receba o URLSession que vai utilizar; e, para o teste, é possível criar um Fake ou um Stub do URLSession (como no exemplo de uma Stub acima).

Com essa solução, quase toda situação de chamadas de API pode ser isolada. No entanto, uma situação específica pode passar despercebida. Para entender melhor, é importante saber que o Xcode possibilita a execução de dois tipos de teste, um teste com uma host application e um teste sem.

Um teste com host application implica que ao executar as suítes de teste o Xcode vai dar lauch da aplicação, ou seja, vai chamar a função:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool

Enquanto um sem host application não realizaria o lauch. Entender isso é importante pois no desenvolvimento costumamos nos preocupar em isolar as chamadas nos nossos testes, mas nem sempre é apenas no teste que uma API é chamada.

Situação 2
Um app que, ao iniciar, coloca uma viewController como root da aplicação e essa view controller chama uma API em seu método de viewDidLoad para carregar uma informação em tela.

Problema
Se os testes forem executados com uma host application, antes de executar os testes aquela view controller vai entrar como root e vai chamar a API.

Solução
Para que isso não ocorra o ideal é que os testes rodem sem uma host application.

Vale ressaltar que, ao fazer isso, para que seja possível ver uma classe de algum arquivo dentro de um teste ele deve ficar visível no target de testes também.

Entretanto, quando o teste precisar necessariamente de uma host, uma segunda solução é criar um Fake AppDelegate que será executado apenas para o target de testes. Basta seguir os seguintes passos:

Além deste fake é possível apenas não iniciar um AppDelegate, assim a host application vai iniciar de maneira mais rápida, mas não será possível utilizar, por exemplo, a UIApplication.shared.

3. Criar o arquivo AppDelegateFake caso queira

Chamadas de Analytics

O analytics é uma ferramenta fundamental para entender melhor o usuário de uma aplicação.

Problema
Uma vez que um teste deve ser isolado, ele não pode comunicar um servidor de analytics que um evento ocorreu, além de comprometer os dados com informações geradas apenas em testes.

Solução
Para resolver este problema pode-se criar um Spy que armazena se um analitycs foi chamado corretamente ou até mesmo quantas vezes ele foi chamado.

Uso de variáveis compartilhadas

Em alguns projetos e frameworks é comum encontrar objetos únicos que permanecem em memória por toda a aplicação. De modo geral, esses objetos costumam seguir o padrão de projeto Singleton ou estão como propriedade de comum, como por exemplo a UIWindow.

Problema
Ao utilizar uma variável compartilhada em um teste podemos criar um problema para um teste subsequente, uma vez que essa variável se manterá em memória e o próximo teste vai utilizá-la com os valores atribuídos no teste anterior, o que pode causar inconsistência de resultados, ainda mais se os testes estiverem executando em ordem aleatória.

Solução
A solução é simples e envolve entender a estrutra que um teste unitário tem.
Um teste unitário geralmente possui 3 etapas: setUp, assert e tearDown.

Essa estrutura é executada para cada teste criado, ou seja, em uma suíte de teste o setUp e o tearDown sempre serão executados respectivamente no começo e final do teste.

Com isso definido, para evitar resultados inconsistentes com variáveis compartilhadas basta guardar o estado delas no setUp e retornar esse estado no tearDown.


E é isso! Ficou alguma dúvida ou tem algo a dizer? Aproveite e deixe um comentário. Quer aprender mais sobre testes na prática? Candidate-se a uma de nossas vagas e vamos aprender juntos. =)

Falando em aprender, aqui embaixo tem algumas referências caso você queira estudar mais sobre o assunto. Até a próxima!

Mocks Aren’t Stubs (Martin Fowler)

Why mocking in iOS tests may not stop network and DB activity entirely (Mike Apostolakis)

A tricky case with Application Tests (AliSoftware)

Concrete

Nós desenvolvemos produtos digitais com inovação, agilidade…

Matheus de Vasconcelos

Written by

Desenvolvedor iOS, Apple Developer Academy Mackenzie Alumni. Atualmente me aprofundando sobre Testes unitários.

Concrete

Concrete

Nós desenvolvemos produtos digitais com inovação, agilidade e excelentes práticas, para que o mercado brasileiro e latino-americano acompanhe a velocidade do mercado digital mundial.

More From Medium

More on Artigos from Concrete

More on Artigos from Concrete

Era uma vez… uma Daily!

Apr 2 · 7 min read

20

More on Artigos from Concrete

More on Artigos from Concrete

Prodcast #6 — Entretenimento

Apr 1 · 1 min read

17

More on Artigos from Concrete

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade