Olá leitor!
Não é segredo que o teste é parte integrante de qualquer desenvolvimento. Nos artigos anteriores, abordamos a arquitetura básica da arquitetura Clean Swift e agora é hora de aprender como cobrir sua unidade com testes. Tomaremos como base o projeto do artigo sobre Trabalhadores e analisaremos os principais pontos.

Teoria
Graças à injeção de dependência e orientada a protocolos , todos os componentes de cena no Clean Swift são independentes entre si e podem ser testados separadamente. Como exemplo, o Interactor depende do Presenter e Worker , mas essas dependências são opcionais e baseadas em protocolo. Assim, o Interactor pode executar seu trabalho (embora inferior) sem o Presenter e Worker , e também podemos substituí-los por outros objetos assinados sob seus protocolos.
Como queremos testar cada componente separadamente, precisamos substituir as dependências por pseudo componentes . Isso nos ajudará espiões (Spy). Spy são objetos de teste que implementam os protocolos que queremos injetar e rastrear as chamadas de método neles. Em outras palavras, criamos o Spy for Presenter e Worker e os injetamos no Interactor para rastrear chamadas de método.

Para ser sincero, acrescentarei que também existem objetos de teste ( duplas de teste ) Dummy , Fake , Stub e Mock . Mas, dentro da estrutura deste artigo, não os afetamos. Leia mais aqui - TestDoubles
Prática
Terminado com as palavras, vamos ao que interessa. Para economizar seu tempo, consideraremos o código abstrato sem entrar em detalhes da implementação de cada método. Detalhes do código do aplicativo podem ser encontrados aqui: CleanSwiftTests
Para cada componente da cena, criamos um arquivo com testes e testes duplos para dependências de componentes ( Test Doubles ).
Um exemplo dessa estrutura:

A estrutura de cada arquivo de teste (para componentes) tem a mesma aparência e segue aproximadamente a seguinte sequência de gravação:
- Declaramos uma variável com SUT (o objeto que vamos testar) e variáveis com suas principais dependências
- Inicializamos o SUT e suas dependências em setUp () e os limpamos em tearDown ()
- Métodos de teste
Como discutimos na teoria, cada componente da cena pode ser testado separadamente. Podemos injetar duplicatas de teste ( Spy ) em suas dependências e, assim, monitorar a operação dos métodos de nosso SUT . Vamos analisar mais de perto o processo de escrever testes usando o exemplo do Interator da cena inicial .
O HomeInteractor depende de dois objetos - Presenter e Worker . Ambas as variáveis na classe têm um tipo de protocolo. Isso significa que podemos criar duplicatas de teste assinadas sob os protocolos HomePresentationLogic e HomeWorkingLogic e injetá-las no HomeInteractor .
final class HomeInteractor: HomeBusinessLogic, HomeDataStore {
Vamos testar dois métodos:
- fetchUsers (:) . Responsável por obter uma lista de usuários pela API . Uma solicitação de API é enviada usando Worker .
- selectUser (:) . Responsável por selecionar o usuário ativo ( selectedUser ) na lista de usuários carregados ( usuários ).
Para começar a escrever os testes do Interactor , precisamos criar espiões que rastrearão a invocação de métodos em HomePresentationLogic e HomeWorkingLogic . Para fazer isso, crie a classe HomePresentationLogicSpy no diretório 'CleanSwiftTestsTests / Stores / Home / TestDoubles / Spies', assine o protocolo HomePresentationLogic e implemente o método deste protocolo.
final class HomePresentationLogicSpy: HomePresentationLogic {
Tudo é extremamente transparente aqui. Se o método presentFetchedUsers ( HomePresentationLogic ) for chamado, definiremos o valor da variável isCalledPresentFetchedUsers como true . Assim, podemos rastrear se esse método foi chamado durante o teste do Interactor .
Usando o mesmo princípio, crie HomeWorkingLogicSpy . Uma diferença, chamamos de conclusão , porque parte do código no Interactor será agrupada no fechamento desse método. Os métodos HomeWorkingLogic lidam com solicitações de rede. Precisamos evitar solicitações reais de rede durante o teste. Para fazer isso, substituí-lo por um teste, que rastreia chamadas de método e retorna dados de modelo, mas não faz nenhuma solicitação à rede.
final class HomeWorkingLogicSpy: HomeWorkingLogic {
Em seguida, criamos a classe HomeInteractorTests , com a qual testaremos o HomeInteractor .
final class HomeInteractorTests: XCTestCase {
Indicamos três variáveis principais - sut , worker e apresentador .
Em setUp (), inicialize os objetos necessários, injete dependências no Interactor e atribua objetos a variáveis de classe.
Em tearDown (), limpamos variáveis de classe para a pureza do experimento.
O método setUp () é chamado antes do início do método de teste, por exemplo testFetchUsers () e tearDown () quando este método conclui seu trabalho. Assim, recriamos o objeto de teste ( sut ) antes de cada método de teste ser executado.
A seguir, os próprios métodos de teste. A estrutura é dividida em 3 blocos lógicos principais - a criação dos objetos necessários, o lançamento do método testado no SUT e a verificação dos resultados. No exemplo abaixo, criamos uma solicitação (no nosso caso, não possui parâmetros), executamos o método fetchUsers (:) Interactor'a e, em seguida, verificamos se os métodos necessários foram chamados em HomeWorkingLogicSpy e HomePresentationLogicSpy . Também verificamos se o Interactor salvou os dados de teste recebidos do Worker em seu DataStore .
func testFetchUsers() { let request = HomeModels.FetchUsers.Request() sut.fetchUsers(request) XCTAssertTrue(worker.isCalledFetchUsers, "Not started worker.fetchUsers(:)") XCTAssertTrue(presenter.isCalledPresentFetchedUsers, "Not started presenter.presentFetchedUsers(:)") XCTAssertEqual(sut.users.count, worker.users.count) }
Testaremos a escolha do usuário por uma estrutura semelhante. Declaramos as variáveis expectationId e expectationName , pelas quais compararemos o resultado da seleção do usuário. A variável users armazena uma lista de teste de usuários que atribuímos ao Interactor . Porque Os métodos de teste são chamados independentemente um do outro, e em tearDown () zeramos os dados, a lista de usuários do Interactor fica vazia e precisamos preenchê-los com algo. E então verificamos se o usuário foi designado no DataStore Interactor'a , depois de chamar sut.selectUser (:) , e se o usuário é o correto.
func testSelectUser() { let expectationId = 2 let expectationName = "Vasya" let users = [ User(id: 1, name: "Ivan", username: "ivan"), User(id: 2, name: "Vasya", username: "vasya91"), User(id: 3, name: "Maria", username: "maria_love") ] let request = HomeModels.SelectUser.Request(index: 1) sut.users = users sut.selectUser(request) XCTAssertNotNil(sut.selectedUser, "User not selected") XCTAssertEqual(sut.selectedUser?.id, expectationId) XCTAssertEqual(sut.selectedUser?.name, expectationName) }
Testar Presenter'a e ViewController'a ocorre no mesmo princípio, com diferenças mínimas. Uma das diferenças é que, para testar o ViewController, você precisará criar uma UIWindow e obter o controlador do Storyboard em setUp () , além de criar objetos Spy em tabelas e coleções. Mas essas nuances variam de acordo com as necessidades.
Para garantir a integridade, recomendo que você se familiarize com o projeto pelo link no final do artigo.
Conclusão
Abordamos os princípios básicos de teste de aplicativos na arquitetura Clean Swift . Ele não possui diferenças fundamentalmente fortes em relação a projetos de teste em outras arquiteturas, todos os mesmos testes, injeção e protocolos. O principal é não esquecer que cada ciclo VIP deve ter uma (e apenas uma!) Responsabilidade. Isso tornará o código mais limpo e os testes mais óbvios.
Link para o projeto: CleanSwiftTests
Ajuda para escrever um artigo: Bastien
Série de artigos
- Visão geral da arquitetura Clean Swift
- Roteador e transmissão de dados na arquitetura Clean Swift
- Trabalhadores na arquitetura Clean Swift
- Teste de unidade na arquitetura Clean Swift (você está aqui)
- Um exemplo de uma arquitetura simples de loja online Clean Swift