27 jul 2016

Teste unitário com swift – Parte 3

Caso você não tenha acompanhado essa série desde o começo poderá consultar os posts anteriores nos links a seguir: Parte 1 e Parte 2

Continuando com nossa série de posts sobre testes unitário em swift, vamos agora criar um teste para uma tela que necessita de dados de serviços externos.

Vamos iniciar nosso trabalho criando uma estrutura para realizar um serviço fictício que deverá buscar as pessoas e retornar um json. Para realizar o serviço estamos utilizando a biblioteca Alamofire e inserimos na chamada da função um callback para apresentar o retorno do servidor como uma string json.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Foundation
import Alamofire

struct PessoasService {
   
    func get(callback: (jsonPessoas: String)->()) {
        Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
            .responseJSON { response in
                if let JSON = response.result.value {
                    callback(jsonPessoas: JSON)
                }
        }
    }
}

Em nossa ViewController vamos instanciar nossa estrutura para utilização e adicionar um label para que ao chamar o serviço caso o resultado seja positivo deverá apresentar o texto “Pessoas carregadas” e caso aconteça algum erro o resultado seja “Falha no carregamento”

1
2
3
    @IBOutlet weak var labelServiceResult: UILabel!

    var pessoasService = PessoasService()

E no carregamento de nossa viewController vamos chamar o serviço.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    override func viewDidLoad() {
        super.viewDidLoad()
       
        carregarDados()
    }
   
    func carregarDados() {
        pessoasService.get { (jsonPessoas) in
            if jsonPessoas.characters.count > 0 {
                self.labelServiceResult.text = "Pessoas carregadas"
            }
            else {
                self.labelServiceResult.text = "Falha no carregamento"
            }
        }
    }

Apesar da prática do TDD, metodologia que estamos utilizando desde de o início desta série, pregar que devemos realizar o teste primeiro para depois fazer a implementação fizemos, desta vez, ao contrário para demonstrar melhor a estrutura para criar um mock do serviço criado.

Então vamos criar a estrutura de nossos testes que deverão testar o resultado do label caso o valor retornado pelo serviço seja positivo ou negativo, conforme estruturas a seguir.

1
2
3
4
5
6
7
    func testSeServicoRetornarOkDeveSetarLabelParaValorPositivo() {
       
    }
   
    func testSeServicoRetornarFalhaDeveSetarLabelParaValorNegativo() {
       
    }

E agora temos nosso primeiro problema: Como testar as regras do serviço independente do serviço?
Na parte 2 desta série aprendemos a testar funções assíncronas e aguardar pelo retorno, porém em um teste de unidade onde vamos testar a regra de negócio, no nosso caso o valor do label ao final da chamada, não faz sentido fazer a chamada pelo serviço pois o mesmo poderá apresentar falhar diversas como, por exemplo, o servidor estar inacessível fazendo com que seu teste falhe por problemas que não fazem parte da unidade a ser testada.

Nesse caso, para testar apenas a nossa unidade poderíamos alterar nossa estrutura para uma classe e criar uma nova classe que deverá sobreescrever os métodos a serem testados, utilizando para isso nossa nova classe.

Porém herdar e ficar sobreescrevendo os métodos não é uma solução muita prática, além de não ser possível utilizar em caso de estruturas.

Para resolver esse problema e criar uma arquitetura que nos permita maior testabilidade vamos criar um protocolo que irá descrever nosso serviço com as funções necessárias as nossa estrutura.

1
2
3
4
5
protocol PessoasServiceProtocol {
   
    func get(callback: (jsonPessoas: String)->())
   
}

E vamos assinar e implementar o novo protocolo em nossa estrutura.

1
2
3
4
5
6
7
8
9
10
11
struct PessoasService: PessoasServiceProtocol {
   
    func get(callback: (jsonPessoas: String)->()) {
        Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
            .responseJSON { response in
                if let JSON = response.result.value {
                    callback(jsonPessoas: JSON)
                }
        }
    }
}

A partir desse ponto, em nossa ViewController, vamos alterar o tipo de nossa variável para o nosso protocolo alterando sua declaração para:

1
    var pessoasService: PessoasServiceProtocol = PessoasService()

Até esse ponto toda nossa implementação permanece funcionando da mesma forma que a implementação anterior porém agora ela nos permite criar um nova estrutura que nos permite o controle sobre o retorno do serviço, facilitando nosso teste.

Vamos agora criar uma nova estrutura que nos permita controlar o resultado da request conforme precisamos para realizar o nosso teste.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct PessoasServiceMock: PessoasServiceProtocol {
   
    let fail: Bool
   
    init(fail: Bool) {
        self.fail = fail
    }
   
    func get(callback: (jsonPessoas: String)->()) {
       
        if fail {
            callback(jsonPessoas: "")
        }
        else {
            callback(jsonPessoas: "{nome: Marcus}")
        }
    }
   
}

Agora podemos injetar a nova estrutura em nosso teste para que possamos validar o resultado, para isso vamos implementar nossos testes conforme código a seguir.

1
2
3
4
5
6
7
8
9
10
11
12
13
    func testSeServicoRetornarOkDeveSetarLabelParaValorPositivo() {
        viewController.pessoasService = PessoasServiceMock(fail: false)
        viewController.carregarDados()
       
        XCTAssertEqual(viewController.labelServiceResult.text!, "Pessoas carregadas")
    }
   
    func testSeServicoRetornarFalhaDeveSetarLabelParaValorNegativo() {
        viewController.pessoasService = PessoasServiceMock(fail: true)
        viewController.carregarDados()
       
        XCTAssertEqual(viewController.labelServiceResult.text!, "Falha no carregamento")
    }

Instanciamos nossa estrutura Mock informando se queremos que a mesma falhe ou não e solicitamos o carregamento dos dados, nesse ponto o serviço é executado com um resultado controlado, permitindo que testamos apenas a unidade específica de nossa regra de negócio.

Leave a Comment