Como testar métodos privados na Swift

Baixe sua cópia gratuita de
O manual ausente
para o desenvolvimento Swift

O guia que eu gostaria de ter quando eu comecei

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy

Unit testing a Swift project is quite different from unit testing a project written in Objective-C. Para aqueles que estão acostumados com a flexibilidade do tempo de execução do Objective-C, pode parecer que suas mãos estão atadas atrás das costas.

Controle de Acesso

Embora o controle de acesso seja uma adição muito bem-vinda, com muitos benefícios, pode complicar o teste unitário, especialmente se você é novo no teste unitário. Você provavelmente sabe que pode aplicar o atributo testable a uma declaração de importação em um alvo de teste para obter acesso a entidades que são declaradas como internas.

import XCTest@testable import Notesclass NotesTests: XCTestCase { ...}

Embora esta seja uma adição conveniente, ela não lhe dá acesso a entidades privadas em um alvo de teste. Isto leva-nos à questão do dia. Como é que se testa unidades de entidades privadas?

Pergunta errada

A resposta curta a esta pergunta é simples. Você não pode acessar entidades privadas de outro módulo e isto também se aplica aos alvos de teste. Simples e simples. É para isso que serve o controle de acesso.

Mas essa não é a resposta para a pergunta. Se você perguntar como unificar as entidades privadas, então você está fazendo a pergunta errada. Mas porque é que isso?

Nenhum dos seus negócios

Porquê declarar uma entidade como privada? Qual é a sua motivação para o fazer? Veja o seguinte exemplo.

import Foundationstruct AccountViewViewModel { // MARK: - Properties let account: Account // MARK: - Public Interface var subscriptionAsString: String { switch account.subscription { case .monthly: return "Monthly Subscription" case .yearly: return "Yearly Subscription" case .trial: return "Trial Subscription" } } var expiresAtAsString: String { // Parse Date let date = parse(date: account.expiresAt) // Initialize Date Formatter let dateFormatter = DateFormatter() // Configure Date Formatter dateFormatter.dateFormat = "YYYY, MMMM" // Convert Date to String return dateFormatter.string(from: date) } // MARK: - Private Interface private func parse(date dateAsString: String) -> Date { let dateFormat: String if dateAsString.contains("/") { dateFormat = "YYYY'/'MM'/'dd" } else { dateFormat = "YYYYMMdd" } // Initialize Date Formatter let dateFormatter = DateFormatter() // Configure Date Formatter dateFormatter.dateFormat = dateFormat if let date = dateFormatter.date(from: dateAsString) { print(date) return date } else { fatalError("Incompatible Date Format") } }}

Quero testar por unidade a estrutura AccountViewViewModel. Como você pode ver, a estrutura AccountViewViewModel expõe duas propriedades internas computadas e também define um método privado. O expiresAtAsString propriedade computada descarrega parte do seu trabalho para o método privado parse(date:). O teste das propriedades internas computadas é simples.

// MARK: - Tests for Subscription as Stringfunc testSubscriptionAsString_Monthly() { let account = Account(expiresAt: "20161225", subscription: .monthly) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.subscriptionAsString, "Monthly Subscription")}func testSubscriptionAsString_Yearly() { let account = Account(expiresAt: "20161225", subscription: .yearly) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.subscriptionAsString, "Yearly Subscription")}func testSubscriptionAsString_Trial() { let account = Account(expiresAt: "20161225", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.subscriptionAsString, "Trial Subscription")}// MARK: - Tests for Expires at as Stringfunc testExpiresAtAsString_20161225() { let account = Account(expiresAt: "20161225", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.expiresAtAsString, "2016, December")}

Mas como testamos o método privado? Não podemos acessar o método privado a partir do alvo do teste. Mas por que devemos testar o método privado por unidade? Nós o marcamos como privado por uma razão. Certo? E isso nos leva à resposta à pergunta com a qual começamos. Não testamos métodos privados.

Unit Testing the Public Interface

Por meio de testes unitários da interface pública da estruturação AccountViewViewModel testamos automática ou implicitamente a interface privada da estrutura. Você tem a tarefa de garantir que a interface pública seja completamente testada. Isto significa que você precisa ter certeza de que cada caminho de código da estrutura AccountViewViewModel é coberto por testes unitários. Em outras palavras, o conjunto de testes unitários deve resultar em uma cobertura completa do código. Isso inclui entidades públicas, internas e privadas.

Se ativarmos a cobertura de código em Xcode e executarmos os testes unitários da estrutura AccountViewViewModel, podemos ver que alguns caminhos de código não são executados.

Code Coverage in Xcode

Isto nos diz que os testes unitários estão incompletos. Podemos ignorar o caminho do código para o erro fatal. Eu nunca testei caminhos de código unitários que resultem em erro fatal, mas isso depende em grande parte de como você usa erros fatais em seus projetos.

Nós podemos aumentar a cobertura de código para a estrutura de AccountViewViewModel adicionando mais um teste unitário.

func testExpiresAtAsString_20161225WithForwardSlashes() { let account = Account(expiresAt: "2016/12/25", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.expiresAtAsString, "2016, December")}

Code Coverage in Xcode

Implementação e Especificação

É importante entender que estamos testando a especificação da estrutura AccountViewViewModel. Não estamos a testar a sua implementação. Embora isto possa parecer semelhante, na verdade é muito diferente. Estamos testando a funcionalidade da estrutura AccountViewViewModel. Não estamos interessados em como ela faz a sua magia sob o capô.

A chave deste artigo é que as entidades privadas não precisam de ser testadas por unidade. O teste unitário é uma forma de teste de caixa negra. Isto significa que não testamos a implementação da estrutura AccountViewViewModel, testamos a sua especificação.

Isto não significa que não estamos interessados na implementação, no entanto. Precisamos ter certeza que o conjunto de testes unitários cobre todos os caminhos de código da entidade que estamos testando. Relatórios de cobertura de código são inestimáveis para realizar isto.

Baixe sua cópia gratuita de
O Manual Ausente
para o Desenvolvimento Swift

O Guia que gostaria de ter quando comecei

Junte 20.000+ Desenvolvedores aprendendo sobre o Desenvolvimento Swift

Baixe sua cópia gratuita

Deixe uma resposta

O seu endereço de email não será publicado.