Swift Testing é o quadro moderno para escrever testes em Swift, construído ao redor de macros expressivas como @Test, #expect e #require. É a sucessora do XCTest e o quadro de teste padrão para novos projetos no Xcode. Em vez de dezenas de métodos de afirmação, você trabalha com uma única macro expressiva que lhe diz exatamente por que um teste falhou.

O quadro abraça Swift Macros e reduz o código de boilerplate que você precisa escrever para testes repetitivos. Neste artigo, guiarei você através de tudo o que você precisa para se tornar produtivo com Swift Testing, com links para guias detalhados para cada recurso ao longo do caminho.

Escrevendo testes usando Swift Testing

Se você é novo em teste, não há muito a comparar. No entanto, se você está acostumado com XCTests, muitas coisas estão mudando ao escrever testes usando o quadro de teste do Swift.

Primeiro, teremos que importar um quadro diferente:

import Testing

Após esta importação, podemos começar a definir nossos primeiros testes. O interessante aqui é que podemos definir testes globalmente:

import Testing
@testable import SwiftTestingPlayground

@Test func personFullName() {
    let person = Person(firstName: "Antoine", lastName: "van der Lee")
    #expect(person.fullName == "Antoine van der Lee")
}

Note que estamos importando SwiftTestingPlayground usando o atributo @testable para permitir acesso a tipos internos como a estrutura Person.

Definimos o teste usando uma nova macro @Test, que substitui o prefixo de método do XCTest test. Dentro do teste, estamos fazendo uso da macro #expect, que substitui afirmações como XCAssert. Certifique-se de esquecer o antigo hábito de escrever test como prefixo. Na verdade, comecei a escrever este artigo com um método de teste chamado testPersonFullName, que não é mais necessário devido à macro @Test.

Depois de executar o teste pela primeira vez, podemos olhar para o navegador de testes e avaliar a hierarquia:

O navegador de testes no Xcode mostra os testes que escrevemos usando Swift Testing.

Enquanto o teste global funciona bem em um pequeno projeto como este, você provavelmente está procurando por uma melhor maneira de organizar seus testes.

A FREE 5-day email course revelando os 5 maiores erros que desenvolvedores iOS cometem com async/await, levando a rejeições na App Store e projetos de migração que demoram meses em vez de dias (mesmo se você já escreve Swift há anos)

Organizando testes no Swift Testing

O framework Swift Testing permite organizar testes usando metadados como traços e tags ou envolvendo testes em estruturas. Vamos ver como uma estrutura pai afeta a organização dos testes.

Neste caso, estamos testando código específico de pessoa. Portanto, faz sentido chamar nossa wrapper PersonTests:

import Testing
@testable import SwiftTestingPlayground

struct PersonTests {
    @Test func fullName() {
        let person = Person(firstName: "Antoine", lastName: "van der Lee")
        #expect(person.fullName == "Antoine van der Lee")
    }
}

Podemos renomear o método de teste para não conter mais “person” já que a estrutura externa torna isso suficientemente claro.

Suponha que você esteja prestes a escrever mais testes em breve, você pode querer ir um passo além e adicionar outra camada chamada Names para se concentrar apenas nos testes relacionados a nomes:

import Testing
@testable import SwiftTestingPlayground

struct PersonTests {
    
    @Test func initialization() {
        let person = Person(firstName: "Antoine", lastName: "van der Lee")
        #expect(person.firstName == "Antoine")
        #expect(person.lastName == "van der Lee")
    }
    
    struct Names {
        @Test func fullName() {
            let person = Person(firstName: "Antoine", lastName: "van der Lee")
            #expect(person.fullName == "Antoine van der Lee")
        }
    }
}

A hierarquia atualizada dentro do navegador de testes fica da seguinte forma:

Você pode agrupar testes usando estruturas como wrappers.
Você pode agrupar testes usando estruturas como wrappers.

Estas são apenas as bases da organização de testes. Em Usando Traits para anotar e personalizar o comportamento dos testes, eu mergulho mais fundo em tags e execução condicional de testes.

Observando mais de perto o macro #expect

A testagem em Swift é impulsionada por macros, das quais a macro #expect possui grande parte da magia. Enquanto antes tínhamos que usar vários métodos de afirmação no XCTest, agora podemos nos concentrar em um único método que funciona magicamente com qualquer entrada.

Alguns exemplos de como o macro #expect se transforma em mensagens de falha claras.Placeholder">
Alguns exemplos de como o macro #expect se transforma em mensagens de falha claras.

Há várias maneiras de definir declarações condicionais, e a macro é inteligente o suficiente para transformá-las em saídas específicas de falhas. Isso fica ainda melhor quando você decide mostrar mais detalhes dentro do Xcode:

O macro #expect trabalha em estreita colaboração com o Xcode para fornecer informações detalhadas de falhas.Placeholder">
O macro #expect trabalha em estreita colaboração com o Xcode para fornecer informações detalhadas de falhas.

Esses detalhes ajudarão você a resolver testes mais rapidamente, tornando mais fácil determinar o que causou a falha. Neste caso, podemos ver que o nome completo da pessoa está definido como “Antoine van der Lee” enquanto nossa entrada String corresponde a “Antoine Lee”.

Você pode aprender mais sobre todas as expressões suportadas em Usando o macro #expect para testes Swift.

Using the #require macro

When using XCTest, we’ve learned that we can use XCTUnwrap to unwrap an optional or fail the test if it returns nil.

func testFullName() throws {
    let person = Person(firstName: "Antoine", lastName: "van der Lee")
    let unwrappedPerson = try XCTUnwrap(person)
    XCTAssertEqual(unwrappedPerson.fullName, "Antoine van der Lee")
}

In Swift Testing, we can create similar behavior by using the #require macro return value:

@Test func fullName() throws {
    let person = Person(firstName: "Antoine", lastName: "van der Lee")
    let unwrappedPerson = try #require(person, "Person should be constructed successfully")
    #expect(unwrappedPerson.fullName == "Antoine van der Lee")
}

You can learn more about this functionality by reading Using the #require macro for Swift Testing.

Reducing boilerplate code using Parameterized Tests

Parameterized tests are an advanced feature of the Swift Testing framework. By using arguments as input for a single test case, you’ll be able to reduce boilerplate code and maintain fewer test cases.

This is an example of a parameterized test which takes two zipped collections as an argument:

@Test(arguments: zip([Feature.userDefaultsEditor, .networkMonitor], [10, 5]))
func testLimitedFreeUsage(_ feature: Feature, tries: Int) {
    #expect(feature.isNumberOfTriesWithinFreeLimit(tries))
}

You can read more about this feature in my article Parameterized tests in Swift: Reducing boilerplate code.

Testing fatal errors with exit tests

Since Swift 6.2, you can finally test code that’s expected to terminate the process, like precondition and fatalError statements. An exit test runs its body inside a new child process and validates how that process exits:

extension BankAccount {
    func withdraw(_ amount: Int) {
        precondition(amount <= balance, "Saldo insuficiente")
        // ...
    }
}

@Test func withdrawingTooMuchTriggersPrecondition() async {
    await #expect(processExitsWith: .failure) {
        let account = BankAccount(balance: 100)
        account.withdraw(200)
    }
}

Antes dos testes de saída, essas rotas de código eram impossíveis de cobrir: a falha levaria o seu teste completo para baixo com ela. Alguns pontos importantes: os testes de saída exigem Swift 6.2 e Xcode 26 ou posterior, eles são executados em macOS e Linux, mas não no iOS, e qualquer valor capturado dentro do corpo deve conformar-se a Sendable e Codable, já que é passado para o processo filho. Você pode ler mais na documentação de testes de saída da Apple.

Anexando valores a testes

O Swift Testing permite anexar valores a um teste, o que facilita diagnosticar falhas, especialmente em CI onde você não pode reexecutar rapidamente um teste. Você pode anexar strings, dados, arquivos e até imagens:

@Test func salesReportAddsUp() async throws {
    let report = await generateSalesReport()
    Attachment.record(report.csvData, named: "relatório-de-vendas.csv")
    try report.validate()
}

Os anexos aparecem dentro do relatório de teste do Xcode após a conclusão da execução. Além disso, tipos que conformam-se a Encodable automaticamente se tornam anexáveis quando você importa Foundation, então você pode gravar seus modelos diretamente. Mais detalhes podem ser encontrados na documentação de anexos da Apple.

Migrando XCTests existentes para Swift Testing

Se você já escreveu testes antes, provavelmente está interessado em como migrá-los para o Swift Testing. A Apple também sabia dessa necessidade e decidiu escrever um artigo de migração detalhado.

No entanto, nem tudo deve ser migrado. Testes automatizados de interface do usuário usando XCUIApplication e testes de desempenho usando XCTMetric ainda exigem XCTest. Ambos os frameworks podem coexistir no mesmo alvo de teste, então você pode migrar incrementalmente: converta as afirmações primeiro, depois reorganize suas suites e finalmente introduza testes parametrizados onde você notar repetição.

Escrevendo código de teste Swift com IA

Se você está usando agentes de codificação de IA como Claude, Codex ou Cursor, não precisa explicar essas melhores práticas em cada prompt. Eu embalei meu conhecimento sobre Swift Testing em uma Habilidade do Agente que ensina seu agente a escrever testes da maneira descrita neste artigo: usando #expect e #require corretamente, reduzindo repetição com testes parametrizados e organizando conjuntos com traços e tags.

Você pode ler como funciona e instalá-lo via Habilidade do Agente de Swift Testing: Escreva testes de alta qualidade com IA.

Conclusão

O Swift Testing transforma a forma como você escreve testes em Swift, com macros expressivas como #expect e #require, testes parametrizados e adições mais recentes como testes de saída e anexos. É a escolha padrão para novos projetos, enquanto o XCTest permanece disponível para automação da interface do usuário e teste de desempenho.

Se você deseja melhorar ainda mais seu conhecimento em testes, verifique a página da categoria Swift Testing. Sinta-se à vontade para entrar em contato comigo ou me enviar um tweet no Twitter se você tiver dicas adicionais ou feedback.