Escrever testes unitários pode ser uma tarefa desafiadora quando o objeto a ser testado possui propriedades ou métodos privados. Neste cenário o uso de mocks não é aplicável e uma solução recorrente é escrever propriedades ou métodos adicionais para validar os membros privados, poluindo o objeto. Neste artigo trago uma solução alternativa que envolve o uso de flags de compilação e atributos privados da linguagem para acessar os membros privados somente no ambiente de teste.
Configuração Inicial
O primeiro passo é adicionar a flag -enable-private-imports
ao seu pacote ou aplicação. Essa flag instrui o compilador a expor membros internos e privados desde que sejam explicitados no código-fonte (falaremos disso mais adiante). No seu arquivo de pacote adicione o parâmetro swiftSettings
à descrição do target e utilize o método unsafeFlags(_:_:)
para inserir a flag mencionada.
.target(
name: "MyAwesomeLibrary",
dependencies: [
...
],
swiftSettings: [
.unsafeFlags(["-enable-private-imports"], .when(configuration: .debug))
])
Neste exemplo restringimos a flag à configuração debug pois não queremos que nosso código de produção exponha os membros privados. Basta utilizar um objeto do tipo BuildSettingCondition para especificar as condições sob as quais as flags de compilação serão aplicadas.
Caso queira expor os membros privados da sua aplicação, adicione a flag na opção Other Swift Flags em Swift Compiler - Custom Flags na aba Build Settings do projeto.
Implementação
Suponhamos que nosso pacote tenha o seguinte código no arquivo Operations.swift:
public struct Operations {
private var count: Int = 0
public func sum(_ x: Int, _ y: Int) -> Int {
count += 1
return x + y
}
}
A variável count
registra o número de operações de soma realizadas — ou seja, a quantidade de vezes que o método sum(_:_:)
foi invocado. Vamos, agora, escrever testes unitários para nosso objeto.
import XCTest
@testable import MyAwesomeLibrary
final class OperationsTests: XCTestCase {
private var operations: Operations!
override func setup() {
super.setup()
operations = .init()
}
func testSum() {
let result = operations.sum(2, 3)
XCTAssertEqual(result, 5)
}
}
O método testSum()
apenas confere se a operação de soma do nosso objeto está correta. Para podermos testar se a variável count
está sendo incrementada a cada chamada temos que expô-la para nossa classe de teste. Para isso adicionaremos o atributo privado @_private(sourceFile:)
à nossa diretiva de importação passando como argumento o nome do arquivo que contém o código-fonte dos membros privados a serem expostos.
import XCTest
@_private(sourceFile: "Operations.swift")
@testable import MyAwesomeLibrary
final class OperationsTests: XCTestCase {
private var operations: Operations!
override func setup() {
super.setup()
operations = .init()
}
func testSum() {
let result = operations.sum(2, 3)
XCTAssertEqual(result, 5)
}
func testCount() {
let _ = operations.sum(2, 3)
let _ = operations.sum(4, 5)
XCTAssertEqual(operations.count, 2)
}
}
O método testCount()
verifica se após duas chamadas de sum(_:_:)
a variável count
é incrementada duas vezes. A assertiva acessa a propriedade count
do objeto operations
sem causar um erro de compilação graças ao atributo @_private(sourceFile: "Operations.swift")
que inserimos ao importar o módulo.
Dessa forma, sempre que precisar testar membros privados de determinado objeto, importe o módulo com o atributo @_private(sourceFile:)
antes e passe como argumento o nome do arquivo que contém esse objeto.
Conclusão
A exposição de membros privados pode facilitar muito a escrita dos testes unitários por eliminar a necessidade de membros auxiliares que de nada servem a não ser observar outros membros. Apenas se atente para que a flag não seja aplicada à compilação de release a fim de evitar a exposição desnecessária de membros privados.
Top comments (0)