iOS — TDD com arquitetura MVP

Victor Magalhães
victormagalhaes
Published in
7 min readSep 16, 2018

--

TDD, do inglês Test Driven Development, como próprio nome sugere, é uma abordagem de desenvolvimento guiada por testes. Resumidamente, primeiro projeta-se os testes para validar as funcionalidades que serão desenvolvidas.

Particularmente, costumo aplicar a metodologia green and red validation que resume-se em criar inicialmente testes que quebrem (red) e após isto, adicionar o código necessário para que estes testes sejam ‘green’.

Lembre-se, TDD não é desenvolver testes após vocês já ter feito todas suas features e sim fazer as mesmas com base nos seus testes. Inicialmente isso parece um pouco confuso e acaba-se adicionando testes apenas para alguns métodos que retornam algum valor ou objeto, tendo uma baixa cobertura.

Já ouvi que TDD é utopia no mercado de trabalho e posso garantir por experiência própria que não. Não deixe de começar a estudar e implementar testes com base neste pensamento, o mercado é rápido e requer estudo contínuo além da utilização de boas práticas.

Para um start point nesta abordagem guiada por testes resolvi escolher uma arquitetura que não tivesse uma alta burocracia (como VIPER) para um app de pequeno porte e possuísse ao mesmo tempo um aspecto clean para facilitar nosso trabalho, ou seja, camadas que não esteja atadas, resultando minha escolha no MVP.

Entendendo o MVP

Parece ficar claro que pelo nome, a arquitetura MVP é baseada em 3 camadas

M: Model
V: View
P: Presenter

Faz sentindo, certo ? rsss

Não pretendo explicar detalhadamente todo o conceito do MVP e sim alguns pontos mais críticos.

Atenção aos Protocols, são core de diversas arquiteturas utilizadas no mercado.

Então agora vamos nos aprofundar em aspectos que considero importantes para um real entendimento desta arquitetura.

A View é considerada burra. Caso isso pareça estranho para você, uma classe burra é uma classe que não apresenta regras de negócio. Neste caso, a View não processa dados, ela apenas exibe o que é recebido pelo presenter e reage as ações do usuário repassando-as ao Presenter para que o mesmo decida como processar estas ações.

O Presenter não conhece UIKit. Ou seja, sabemos que o presenter irá executar as regras de negócio desta arquitetura, porém, por não possuir conhecimento do UIKit, é impedido de obter uma referência da UIViewControllere executar a navegação (que também não será executada pela View).

Estas características acabam limitando os desenvolvedores a abordagens que por vezes acabam por si quebrando estes conceitos (não recomendo), como adicionar UIKit no presenter ou até mesmo navegar pela UIViewController.

Porém seguindo ☯️ para as abordagens não tão boas, há melhores, seja criando uma camada de navegação como um FlowController (❤️) ou até mesmo um Router (muito conhecido para quem já trabalha com VIPER).

Por se tratar de um app introdutório focado em testes e não em escalabilidade, flexibilidade e eficiência, escolhi o que para mim me parecia mais simples de implementar, e neste caso adicionei um Router aonde logicamente, também será executado testes, chegando ao seguinte resultado.

Mãos na massa.

Para abranger algumas situações de teste, achei legal uma cena no estilo “alteração de senha”, envolvendo três componentes diferentes, como UITextField, UILabel e UIButton. Resultando nesta tela ganhadora milhares de prêmios de Design.

Tendo nosso Mock de layout final, podemos iniciar a terraplanagem, criando a seguinte hierarquia de arquivos com adição do Router, correspondendo o que foi explicado anteriormente.

Bom, não sendo um grande fã de Interface Builder simplesmente por não conseguir me sentir muito bem com constraints visuais e sentir que por código as coisas ficam bem mais ‘claras’ e flexíveis, optei por uma abordagem de layout totalmente programática (com exceção da launch screen).

Vamos iniciar removendo o storyboard, e removendo o Main Interface.

Após isto não se esqueça de reviver seu app! Inicialize seu módulo root no AppDelegate.

Analisando as inicializações, você percebe que o Router está guardando uma referência da View para navegação e o Presenter guardando a referência do Router, sem precisar saber nada de UIKit. Easy!

A ideia é que você passe como parâmetros para o módulo, um título e um placeholder para cada seção de input, além do título do botão que será utilizado para salvar. A regra para validação é que o usuário digite a senha atual de acordo com a do “sessão do usuário”,

simulando uma session de login contendo os dados do usuário. Além disto a nova senha não pode ser igual nem mesmo vazia.

Ok, estrutura montada, regras de negócio esclarecidas, hora de criar nossa folha de testes. Existem diversas abordagens para iniciar sua cobertura de testes, cada desenvolvedor pode defender um ponto ou outro e acredito que a abordagem que estou mostrando aqui não seja a bala de prata, valendo sempre a mente aberta para outras perspectivas.

Costumo partir pelo contexto da View sendo inicializada, ou seja, recebendo os parâmetros da inicialização e passando para os componentes. Para isto testamos se cada parâmetro recebido na inicalização é o que está sendo recebido na View e se está sendo recebido no método correto.

A segunda etapa está mais ligada as ações que são tomadas na tela em questão. Ou seja, o que você espera quando o usuário clicar em salvar?

Caso a validação falhe?

Caso a validação tenha sucesso?

Navegará para outra tela?

Passará algum valor para a tela navegada?

Apresentará um alerta?

Você começa a perceber que a partir de uma ação, começam a ser criados contextos, alguns podendo ser simplesmente descartados por questões de funcionamento do app em si, como a questão de navegar para outra tela ou apresentar um Alert ao usuário ou questões de verificar se o Alert de erro está sendo apresentado realmente quando há o erro e o de sucesso realmente na ocasião de sucesso.

Lógicamente, é testado dentro dos contextos propostos, se as ações do Router estão sendo chamadas de maneira correta e se a passagem de valores pelo o mesmo também estão corretas, garantindo uma navegação sólida além de uma melhor abrangência de testes.

Para um template mais genérico, resolvi não integrar biblioteca de terceiros, utilizando XCTest nativo.

O resultado da folha de testes para a tela de input de dados foi o seguinte:

Após testar, quebrar, testar, quebrar, testar e quebrar mais um pouco.

Voila! Testes verdes, feature se solidificando.

O interessante é que lendo a folha de testes, você começa a perceber outros cenários de testes, e isso é algo incrível que faz parte do mundo de desenvolvimento. Você verá ao longo do seu desenvolvimento (principalmente em time), é que a construção de uma aplicação sólida é gradual, muitas vezes verá testes sem sentidos, muitas vezes, verão testes que você não viu e deste modo chegarão a resultados sensacionais.

Analisando os testes é possível visualizar qual o comportamento do aplicativo, verifica-se a existência de testes de navegação para uma tela que resulta no sucesso ou não da alteração de senha. Esta tela, recebe um status boleano e exibe um modal ao usuário, que quando clicado em qualquer parte sobre ele é executado uma ação de dismiss para uma nova alteração.

A folha de testes para esta tela de resultado de validação pode ser observada a a seguir.

Mais um pouco de testar, quebrar, testar, quebrar, testar e quebrar mais um pouco.

Voila! Testes verdes mas não sonhe com idealidade ainda há code review e QA para a feature ser definida como pronta!

Por fim chegamos ao seguinte resultado:

Particularmente, não vejo o XCTest como a alternativa mais “matadora” para testes, principalmente para equipes com QA’s. Aconselho a utilização do Quick/Nimble para uma estruturação melhor dos testes além de como os mesmos estão sendo descritos.

Deixo por fim o link do repositório proposto e do Quick/Nimble para curiosos:
https://github.com/victorpanitz/iOS-Basic_MVP_with_UnitTests
https://github.com/Quick/Nimble

--

--