XCTest: conceitos-chave, classes XCTestCase e escrita de testes

Autor: IT Sectr Publicado: 2026-04-08 Tempo de leitura: 11 min

XCTest é um framework da Apple para testes unitários e de integração de aplicações para iOS, macOS, watchOS e tvOS. XCTest faz parte do Xcode e suporta a escrita de testes em Swift e Objective-C. Ao contrário de frameworks de terceiros (Quick, Nimble), XCTest é a solução oficial da Apple e está totalmente integrado com Xcode Server e CI/CD. De acordo com a Apple Developer (2024), XCTest é usado em 94% das aplicações iOS do top 100 da App Store. XCTest fornece uma base estável para escrever testes unitários e testes de UI sem dependências externas.

Principais pontos

  • XCTest — o framework oficial da Apple para testes unitários e testes de UI em Swift e Objective-C.
  • XCTestCase — a classe base para todos os testes, que fornece setUp, tearDown e métodos de asserção.
  • Asserções — XCTAssertTrue, XCTAssertEqual, XCTAssertNil e outras para verificar resultados esperados.
  • XCTestExpectation — um mecanismo para testar código assíncrono com espera de cumprimento.
  • Testes de desempenho — medição do tempo de execução do código através do método measure(metrics:) com limites.

O que é XCTest?

XCTest é um framework para testes unitários, de integração e de UI desenvolvido pela Apple e integrado no Xcode desde a versão 5.0 (2013). XCTest substituiu o OCUnit (SenTestingKit) e forneceu uma API moderna em Swift com suporte para testes assíncronos, testes de desempenho e integração com Xcode Server. De acordo com Swift.org (2024), XCTest é a base para testes em todos os projetos da Apple, incluindo o Swift Package Manager, que usa XCTest para autovalidação.

XCTest funciona em conjunto com o Xcode Test Navigator e Report Navigator, que mostram a árvore de testes, o histórico de execuções e comparam resultados entre builds. Test Navigator permite executar um único teste, um grupo de testes ou todo o conjunto sem modificar código. Os resultados são exibidos com ícones verdes (aprovados), vermelhos (falhos) e amarelos (ignorados). De acordo com a Apple WWDC (2024), o Xcode 16 melhorou a execução paralela de testes em 40% usando vários simuladores.

XCTest suporta as plataformas: iOS 8.0+, macOS 10.10+, watchOS 2.0+, tvOS 9.0+. Cada plataforma tem a mesma API, permitindo escrever testes multiplataforma. Swift Testing — um novo framework da Apple (anunciado em 2024) — complementará o XCTest no futuro, mas não o substituirá completamente. XCTest continua sendo o framework principal de testes no ecossistema Apple.

XCTestCase — a classe base para testes

XCTestCase é a classe base da qual todas as classes de teste em XCTest herdam. Ela fornece o ciclo de vida do teste: `setUp()` é chamado antes de cada teste, `tearDown()` após cada teste. setUp é usado para inicializar objetos e mocks, tearDown para limpar recursos. setUpWithError e tearDownWithError permitem lidar com erros de inicialização sem try-catch em cada teste.

Cada método cujo nome começa com `test` é automaticamente reconhecido pelo Xcode como um teste. Alternativamente, a macro `@Test` (Swift Testing) pode ser usada. Os nomes dos testes devem ser descritivos: `testLoginWithValidCredentials` é melhor que `testLogin1`. Documentar testes através de comentários é uma boa prática, mas o Xcode também permite adicionar descrições através de User-Defined Attributes.

swift
import XCTest

class UserServiceTests: XCTestCase {

    var sut: UserService!
    var mockSession: MockURLSession!

    override func setUp() {
        mockSession = MockURLSession()
        sut = UserService(session: mockSession)
    }

    override func tearDown() {
        sut = nil
        mockSession = nil
    }

    func testFetchUser_ReturnsDecodedUser() {
        let json = "{\"id\": 1, \"name\": \"Alice\"}"
        mockSession.setResponse(json)
        let user = try await sut.fetchUser(id: 1)
        XCTAssertEqual(user.name, "Alice")
    }
}

O exemplo acima demonstra a estrutura padrão do XCTestCase. sut (System Under Test) é uma convenção de nomenclatura para o objeto sendo testado. MockURLSession substitui a rede real, permitindo que UserService seja testado isoladamente. O princípio de “um teste — uma asserção” simplifica a depuração: se um teste falha, o desenvolvedor sabe imediatamente qual funcionalidade está quebrada. Cada teste XCTestCase deve verificar um cenário ou uma asserção.

Asserções em XCTest

Grupo básico de asserções

XCTAssertTrue e XCTAssertFalse são asserções básicas para verificar valores booleanos. XCTAssertTrue(expression) passa se expression == true. XCTAssertEqual verifica a igualdade de dois valores com suporte para todos os tipos que implementam Equatable. Para números de ponto flutuante, XCTAssertEqual com o parâmetro accuracy é usado para considerar a precisão do cálculo. De acordo com o Google Testing Blog (2024), XCTAssertEqual cobre 70% de todas as verificações em um conjunto de testes típico.

Asserções nil e erros

XCTAssertNil e XCTAssertNotNil verificam valores opcionais para nil. Essas asserções são críticas em Swift, onde tipos opcionais são amplamente usados. XCTAssertThrowsError verifica se o código lança um erro esperado. XCTUnwrap é uma asserção que desembrulha um valor opcional e falha com uma mensagem clara se o valor for nil. A comparação de strings via XCTAssertEqual usa comparação literal, não semântica. XCTAssertNoThrow é a asserção emparelhada para verificar que o código não lança um erro.

AsserçãoPropósitoExemplo
XCTAssertEqualVerificação de igualdadeXCTAssertEqual(a, b)
XCTAssertTrueVerificação de verdadeXCTAssertTrue(result)
XCTAssertNilVerificação de nilXCTAssertNil(error)
XCTAssertThrowsErrorVerificação de erroXCTAssertThrowsError(try parse(""))
XCTUnwrapDesembrulhamento optionalXCTUnwrap(value)

XCTestExpectation e testes assíncronos

XCTestExpectation é um mecanismo para testar código assíncrono. O teste cria uma expectativa com um nome descritivo, passa-a para uma operação assíncrona e chama `wait(for:timeout:)`. Se a expectativa não for cumprida dentro do tempo limite, o teste falha. Tempo limite padrão é de 10 segundos, mas para operações rápidas recomenda-se definir 1–3 segundos para acelerar o tempo total de teste.

XCTWaiter é uma alternativa mais flexível ao wait(for:timeout:). XCTWaiter permite esperar múltiplas expectativas, configurar a ordem de execução e lidar com timeouts programaticamente. Ao contrário de wait, XCTWaiter retorna `XCTWaiter.Result`, que pode ser analisado. Delegate XCTWaiterDelegate notifica sobre violações da ordem de expectativas e timeouts.

swift
func testAsyncLogin() {
    let expectation = XCTestExpectation(description: "login")
    var resultUser: User?

    sut.login(email: "a@b.com", password: "123") { user in
        resultUser = user
        expectation.fulfill()
    }

    wait(for: [expectation], timeout: 3)
    XCTAssertNotNil(resultUser)
    XCTAssertEqual(resultUser?.email, "a@b.com")
}

No exemplo, XCTestExpectation é usado para testar um login assíncrono. fulfill() é chamado dentro do closure do callback, sinalizando que a operação assíncrona foi concluída. Se fulfill() não for chamado em 3 segundos, o teste falha com timeout. Após a espera bem-sucedida, as asserções são realizadas para verificar o resultado. Múltiplas expectativas podem ser passadas como um array e aguardar a conclusão de todas.

Testes de desempenho com XCTest

measure(metrics:) é um método XCTestCase para criar testes de desempenho. O bloco de código dentro de measure é executado 10 vezes consecutivas, e o XCTest coleta estatísticas: tempo médio, mediana, desvio padrão. Metrics é um array de métricas rastreadas: XCTClockMetric (tempo), XCTMemoryMetric (memória), XCTStorageMetric (disco) e XCTCPUMetric (processador). De acordo com a Apple WWDC (2024), testes de desempenho com XCTCPUMetric são úteis para detectar regressões em algoritmos.

Baseline (linha de base) para testes de desempenho é definida no Xcode Test Plan. Se o tempo de execução exceder a linha de base em uma porcentagem definida (padrão 10%), o teste é considerado falho. A linha de base é atualizada manualmente após confirmar que a mudança de desempenho é esperada. Test Plan no Xcode permite agrupar testes de desempenho por configurações: debug/release, diferentes dispositivos, diferentes versões do iOS.

swift
func testArraySortPerformance() {
    let numbers = (1...10000).shuffled()
    measure(metrics: [XCTClockMetric()]) {
        let _ = numbers.sorted()
    }
}

Este teste de desempenho mede o tempo de ordenação de um array de 10.000 elementos. XCTClockMetric captura o tempo real de execução. Se após alterar o algoritmo de ordenação o tempo aumentar em 10% ou mais, o teste indicará uma regressão. Os testes de desempenho do XCTest são especialmente úteis para: algoritmos de processamento de dados, renderização de componentes de UI, operações de banco de dados e requisições de rede.

Organização de testes e integração CI/CD

Estrutura do projeto de testes

A estrutura do projeto de testes no XCTest segue a convenção: um arquivo de teste por classe, colocado em um diretório separado `<TargetName>Tests`. Os nomes dos arquivos correspondem aos nomes das classes testadas com o sufixo `Tests`: `UserService.swift` → `UserServiceTests.swift`. Os Test Targets no Xcode são configurados separadamente para testes unitários e testes de UI, permitindo que sejam executados independentemente. Schemes no Xcode gerenciam a configuração de build e o conjunto de testes a serem executados.

Integração CI/CD

Xcode Cloud e GitHub Actions suportam a execução do XCTest via `xcodebuild test -scheme App -testPlan SmokeTest`. O pipeline de CI inclui: build → execução de testes unitários → execução de testes de UI → publicação do relatório. O relatório JUnit é gerado via `xcodebuild` com a opção `-resultBundlePath` e pode ser importado em qualquer ferramenta de CI. Code Coverage é um recurso integrado do XCTest que mostra quais linhas de código são cobertas pelos testes. O limite mínimo de cobertura para código de produção é de 70% para a lógica de negócio crítica.

Xcode Cloud e GitHub Actions suportam a execução do XCTest via `xcodebuild test -scheme App -testPlan SmokeTest`. O pipeline de CI inclui: build → execução de testes unitários → execução de testes de UI → publicação do relatório. Relatório JUnit é gerado via `xcodebuild` com a opção `-resultBundlePath` e pode ser importado em qualquer ferramenta de CI. Bitrise e Jenkins têm etapas prontas para XCTest.

Code Coverage é um recurso integrado do XCTest que mostra quais linhas de código são cobertas pelos testes. Xcode exibe a cobertura em verde (coberto), vermelho (não coberto) e amarelo (parcialmente coberto). Limite mínimo de cobertura para código de produção é de 70% para a lógica de negócio crítica. De acordo com o Google Testing Blog (2024), exigir 80% de cobertura para todos os módulos leva a “testes vazios” que não verificam a lógica, apenas executam código.

Perguntas frequentes

Como o XCTest difere do Quick e Nimble?

XCTest é o framework oficial da Apple com integração direta no Xcode. Quick e Nimble são bibliotecas de terceiros que fornecem sintaxe BDD e asserções mais legíveis. Quick e Nimble são convenientes para Acceptance Testing, mas o XCTest é mais confiável para testes unitários devido à ausência de dependências externas.

Como testar código assíncrono no XCTest?

O código assíncrono é testado via XCTestExpectation + `wait(for:timeout:)` ou através de métodos `async/await` do XCTest (iOS 13+). Para APIs baseadas em callback, uma expectativa é criada e cumprida no closure. Para async/await, asserções padrão são usadas em funções async.

Como simular dependências no XCTest?

O XCTest não inclui um framework de mocking integrado. Mocking é implementado através de protocolos: uma classe mock é criada que implementa o mesmo protocolo que a dependência real. Para geração automática de mocks, são usados Cuckoo, SwiftyMocky ou mocks manuais. A injeção de dependência através de inicializadores é uma condição obrigatória para testabilidade.

O XCTest pode ser executado em dispositivos reais?

Sim, o XCTest é executado em dispositivos reais via Xcode ou xcodebuild com o parâmetro `-destination 'platform=iOS,name=iPhone 15'`. Testes de UI em dispositivos reais produzem resultados mais precisos do que em simuladores. Para execução em farms de dispositivos, são usados BrowserStack, Sauce Labs ou Firebase Test Lab.

O que há de novo no Swift Testing comparado ao XCTest?

Swift Testing (2024) é um novo framework da Apple com macros `@Test`, `@Suite` e `@Expect`. Ele fornece parametrização de testes integrada, agrupamento em suites e sintaxe mais legível. O Swift Testing coexiste com o XCTest e não o substitui. O XCTest continua sendo o framework principal para testes de UI e testes de desempenho.

Resumo

  • XCTest — o framework oficial de testes da Apple, integrado no Xcode e compatível com Swift e Objective-C.
  • XCTestCase fornece o ciclo de vida setUp/tearDown e um conjunto de asserções para testes unitários.
  • XCTestExpectation e XCTWaiter são mecanismos para testar código assíncrono com callbacks e async/await.
  • Testes de desempenho usam measure(metrics:) com suporte para XCTClockMetric, XCTMemoryMetric e XCTCPUMetric.
  • Asserções — XCTAssertEqual, XCTAssertTrue, XCTAssertNil, XCTAssertThrowsError, XCTUnwrap e outras.
  • Integração CI/CD via xcodebuild, Xcode Cloud e GitHub Actions com geração automática de relatórios.
  • Swift Testing — um novo framework da Apple que complementa o XCTest para testes unitários e parametrização.

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também