Coloque a preparação assíncrona compartilhada do fixture no init() async throws de uma suíte do Swift Testing. O Swift Testing conclui esse inicializador antes de chamar um método de teste de instância, de modo que o método recebe um estado pronto para uso. No XCTest, use setUp() async throws.
Este guia é para um desenvolvedor Mac que prepara dados de teste unitário locais no Xcode. A tarefa é concluir a inicialização assíncrona antes do início das verificações, usando cálculos determinísticos com entradas sintéticas. A Apple documenta a abordagem com inicializador em Migrating a test from XCTest.
Como o Swift Testing aguarda a inicialização do fixture?
Para um método de teste de instância, o Swift Testing inicializa o tipo da suíte que o contém e, em seguida, chama o método nessa instância. A suíte precisa ter um inicializador que possa ser chamado sem argumentos; esse inicializador pode ser assíncrono, lançar erros ou ambos. Cada método de teste de instância é executado em uma instância distinta. Essas regras de ciclo de vida estão documentadas em Organizing test functions with suite types.
A dependência resultante é simples: a preparação do fixture retorna, o inicializador termina e o método de teste começa. Seu inicializador precisa realmente aguardar a operação de preparação. Apenas iniciar o trabalho dentro dele não estabelece essa dependência.
Use o guia de decisão a seguir para escolher onde colocar a preparação:
| Situação | Local da preparação | O que estabelece que está pronto? |
|---|---|---|
| Vários testes de instância precisam da mesma receita de fixture | init() async throws da suíte |
O inicializador aguardado termina antes de cada método |
| Apenas um teste precisa do fixture | Corpo de teste async throws |
Um try await explícito antes das verificações |
Um teste existente herda de XCTestCase |
setUp() async throws |
O XCTest conclui a configuração assíncrona antes do método |
Um teste do Swift Testing é static ou global |
O corpo do teste ou um helper chamado explicitamente | O teste aguarda explicitamente a preparação |
Esta tabela aplica as regras de ciclo de vida documentadas pelas frameworks à localização do fixture. Em particular, um inicializador de suíte é um ponto de preparação por instância; isso não significa que a preparação aconteça uma única vez para toda a suíte. Consulte as regras de inicialização de suítes e a sequência de configuração do XCTest da Apple.
Adicione no Xcode um exemplo completo de fixture assíncrono
Use o Xcode 16 ou posterior com o suporte ao Swift Testing incluído. O projeto Swift documenta que o Swift Testing está incluído no Swift 6 e no Xcode 16, e que não é necessária uma dependência de pacote adicional. Consulte o README do Swift Testing oficial.
Em um destino de teste unitário existente, escolha File > New > New File From Template e selecione Swift Testing Unit Test. Substitua o conteúdo do arquivo gerado pelo exemplo completo abaixo. Um pacote existente de testes unitários do XCTest também pode conter testes do Swift Testing; não é necessário um pacote separado. Estas etapas seguem as instruções da Apple em Adding tests to your Xcode project.
import Testing
enum FixtureError: Error {
case invalidInput
}
private actor SquareCalculator {
let values: [Int]
init(values: [Int]) {
self.values = values
}
func squares() -> [Int] {
values.map { $0 * $0 }
}
}
struct CalculationFixture {
let squares: [Int]
let total: Int
static func make(values: [Int]) async throws -> Self {
guard !values.isEmpty,
values.count <= 100,
values.allSatisfy({ (0...100).contains($0) }) else {
throw FixtureError.invalidInput
}
let calculator = SquareCalculator(values: values)
let squares = await calculator.squares()
return Self(
squares: squares,
total: squares.reduce(0, +)
)
}
}
@Suite
struct CalculationTests {
let fixture: CalculationFixture
init() async throws {
fixture = try await CalculationFixture.make(values: [2, 4, 6])
}
@Test
func squaresAreReady() {
#expect(fixture.squares == [4, 16, 36])
}
@Test
func totalIsReady() {
#expect(fixture.total == 56)
}
}
O exemplo usa um ator local real para realizar o cálculo. Chamar seu método isolado de fora do ator exige await. Não há atraso, entrada aleatória, dependência externa nem operação em segundo plano que precise terminar no momento certo. O resultado permanece determinístico, independentemente de a execução ser realmente suspensa.
A fábrica do fixture rejeita entradas vazias, mais de 100 valores e valores fora de 0...100. Esses limites ilustrativos mantêm os cálculos pequenos: cada quadrado é no máximo 10.000 e o total é no máximo 1.000.000. Valores inválidos geram um erro, em vez de serem removidos ou substituídos silenciosamente.
Para a entrada fornecida, o cálculo esperado é 2² + 4² + 6² = 4 + 16 + 36 = 56. O inicializador armazena o fixture concluído em uma propriedade não opcional let. Assim, os métodos de teste não precisam de uma flag de prontidão nem de desembrulhar um opcional.
Os próprios métodos podem permanecer síncronos porque o pré-requisito assíncrono já foi concluído no inicializador. Quando o trabalho assíncrono pertence ao corpo de um teste, o padrão documentado pela Apple é marcar essa função como async e aguardar a operação diretamente. Consulte Testing asynchronous code.
A aritmética sintética foi verificada localmente com Python. O Swift não está disponível neste ambiente de pesquisa, portanto os exemplos Swift foram inspecionados, mas não foram compilados nem executados aqui. Os resultados esperados acima descrevem o cálculo do exemplo, não uma execução de teste no Xcode.
Por que iniciar uma tarefa não conclui a configuração?
Um erro comum é iniciar Task { ... } em um inicializador síncrono e retornar imediatamente. Nesse caso, o inicializador terminou de iniciar o trabalho, mas esse trabalho ainda pode estar em andamento quando o método de teste começar.
O guia da linguagem Swift descreve Task { ... } como a criação de uma tarefa não estruturada. O identificador retornado pode ser usado para aguardar o resultado por meio de await handle.value. Descartar o identificador remove esse ponto explícito de espera. Consulte a seção Concorrência não estruturada do livro oficial do Swift.
Para inicializar um fixture, aguardar diretamente a fábrica em um inicializador assíncrono costuma ser a forma mais clara de expressar a dependência. Se um helper existente inicia trabalho internamente e retorna antes de esse trabalho terminar, aguardar o helper só aguarda seu retorno. Altere o contrato de conclusão para que retornar signifique que o fixture está pronto, ou exponha o resultado de uma tarefa que o chamador possa aguardar.
Adicionar uma pausa de duração fixa não expressa essa dependência. Isso apenas espera o tempo passar, deixando a prontidão depender de a preparação terminar dentro desse intervalo. Da mesma forma, tornar o método de teste async apenas permite chamadas assíncronas; o método ainda precisa aguardar a operação da qual depende.
Qual é a configuração equivalente no XCTest?
O XCTest oferece um hook de configuração assíncrona dedicado. A ordem documentada antes de cada método de teste é setUp() async throws, depois setUpWithError() e, por fim, setUp(), síncrono. O método de teste vem depois desses hooks. Consulte Set Up and Tear Down State in Your Tests.
Para comparar o mesmo fixture no XCTest, adicione o seguinte arquivo complementar completo ao mesmo destino de teste unitário do primeiro exemplo. Ele importa XCTest e usa CalculationFixture, definido como visível no módulo. Mantenha as definições do helper no primeiro arquivo; não as duplique.
import XCTest
final class CalculationXCTests: XCTestCase {
private var fixture: CalculationFixture?
override func setUp() async throws {
try await super.setUp()
fixture = try await CalculationFixture.make(values: [2, 4, 6])
}
func testTotalIsReady() throws {
let readyFixture = try XCTUnwrap(fixture)
XCTAssertEqual(readyFixture.squares, [4, 16, 36])
XCTAssertEqual(readyFixture.total, 56)
}
}
O método XCTest também pode ser síncrono: a preparação assíncrona ocorre na configuração. A propriedade opcional acomoda a fase de configuração separada do XCTest, e XCTUnwrap transforma a ausência do fixture em uma falha explícita. A Apple documenta esse padrão de desembrulhamento em seu guia de migração.
Ao converter este teste para Swift Testing, transfira a preparação para o inicializador da suíte e use import Testing, @Test e as expectativas do Swift Testing. Um método chamado apenas setUp em uma suíte comum do Swift Testing não é o mecanismo de configuração documentado; o caminho de migração da Apple substitui a família de métodos de configuração pela inicialização.
Verifique estes limites se a inicialização ainda parecer antecipada
Primeiro, verifique se o teste é um método de instância. Um teste static não precisa de uma instância da suíte, portanto não pode depender da inicialização automática de instância. Um teste global também não tem uma instância que o contenha. Em qualquer um dos casos, construa e aguarde o fixture no corpo do teste. As formas de teste compatíveis estão descritas em Migrating a test from XCTest.
Segundo, inspecione toda a cadeia de chamadas da preparação. Encontre a operação que realmente produz o estado pronto e garanta que cada chamador a aguarde. No exemplo, o cálculo do ator termina antes de a fábrica retornar, e a fábrica retorna antes de o inicializador terminar. Nenhum trabalho necessário às verificações escapa dessa sequência.
Terceiro, permita que os erros de configuração se propaguem. Se CalculationFixture.make lançar um erro, a instância da suíte não poderá concluir a inicialização, então o método de teste de instância não poderá prosseguir nessa instância. Evite substituir uma preparação malsucedida por um fixture vazio usando try? e um valor alternativo, a menos que esse comportamento alternativo seja explicitamente o que está sendo testado. O erro de entrada inválida do exemplo impede deliberadamente a criação de um fixture aparentemente utilizável.
Por fim, diferencie a ordem de inicialização do agendamento de testes. Por padrão, o Swift Testing executa os testes em paralelo. O trait .serialized de uma suíte pode fazer com que os testes contidos nela sejam executados em série, mas a serialização não transforma uma tarefa não aguardada em uma preparação aguardada, nem faz o inicializador da suíte ser executado apenas uma vez. A Apple documenta o escopo do trait em Running tests serially or in parallel.
Para este cálculo local, mantenha o fixture imutável e prepare-o independentemente para cada teste de instância. A verificação essencial é a cadeia de dependências: a operação de preparação retorna um fixture concluído, o inicializador aguarda esse resultado e o teste lê a propriedade inicializada. Isso dá ao teste uma condição concreta de prontidão, sem depender de suposições de tempo.
Fontes
- Organizing test functions with suite types — Apple
- Migrating a test from XCTest — Apple
- Set Up and Tear Down State in Your Tests — Apple
- README do Swift Testing — projeto Swift
- Adding tests to your Xcode project — Apple
- Testing asynchronous code — Apple
- The Swift Programming Language: Concurrency — projeto Swift
- Running tests serially or in parallel — Apple