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 é 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 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.
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.
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.
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ção | Propósito | Exemplo |
|---|---|---|
| XCTAssertEqual | Verificação de igualdade | XCTAssertEqual(a, b) |
| XCTAssertTrue | Verificação de verdade | XCTAssertTrue(result) |
| XCTAssertNil | Verificação de nil | XCTAssertNil(error) |
| XCTAssertThrowsError | Verificação de erro | XCTAssertThrowsError(try parse("")) |
| XCTUnwrap | Desembrulhamento optional | XCTUnwrap(value) |
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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
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.
Leia também