Testando membros privados com Private Imports
Tags: ios, swift, testing
Published: 2023-02-26 22:38:13

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`](https://developer.apple.com/documentation/packagedescription/target/swiftsettings) à descrição do target e utilize o método [`unsafeFlags(_:_:)`](https://developer.apple.com/documentation/packagedescription/swiftsetting/unsafeflags(_:_:)) para inserir a flag mencionada.

```swift
.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](https://developer.apple.com/documentation/packagedescription/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:

```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.

```swift
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.

```swift
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.