Documents
Selective Testing in Xcode Projects
Selective Testing in Xcode Projects
Type
Document
Status
Published
Created
Nov 29, 2025
Updated
Mar 27, 2026
Updated by
Dosu Bot

How Selective Testing Works#

Tuist Selective Testing enables you to run only the Xcode tests affected by your latest changes, significantly reducing test execution time for large projects. It works by hashing the contents of each test target. The hash of a test target includes its own source files and the hashes of all its dependencies—including targets built by foreign build systems (such as Kotlin Multiplatform, Rust, or CMake) that are integrated via Tuist's .foreignBuild target factory. On each test run, Tuist compares the current test target hashes to those from the last successful run. Only the tests for targets whose hash has changed are executed; others are skipped. This mechanism is available exclusively for projects generated by Tuist, offering integration features such as binary caching.

The granularity of Tuist Selective Testing is at the target (module) level. Tuist cannot detect in-code dependencies between individual files or test cases, so it determines changes based on the target as a whole. Because the hash of a test target incorporates the hashes of its dependencies, any change to a dependency will cause the test target's hash to change and trigger its tests to run. This includes foreign build targets: if the build script or any declared input (file, folder, glob, or script) for a foreign build target changes, the hash of any test target depending on it will change, and those tests will be re-executed.

For example, consider a project with three modules: Core, FeatureA, and FeatureB, each with its own tests. If only FeatureA changes, only FeatureATests will run because only its hash changed. If Core changes, all tests that depend on it (CoreTests, FeatureATests, and FeatureBTests) will run because their hashes now incorporate the changed Core hash. If a foreign build target (such as a Kotlin Multiplatform xcframework) changes its script or inputs, all tests depending on it will also be re-executed because their hashes will reflect the change.

Optimized Build and Test Workflow#

When using a split build and test workflow (common in CI environments with parallel runners), Tuist optimizes the test stage to eliminate redundant overhead:

Build Stage (tuist test --build-only): The selective testing graph (a JSON file containing target-to-hash mappings) is embedded into the .xctestproducts bundle. This bundle contains all the information needed to run tests without regenerating the project.

Test Stage (tuist test --without-building -testProductsPath <path>): When the .xctestproducts bundle contains the embedded selective testing graph, Tuist automatically skips project generation and graph computation. It reads the pre-computed hashes from the bundle and proceeds directly to running tests, eliminating 30-60+ seconds of overhead per test runner.

This optimization is automatic—no configuration is required. When you use --build-only followed by --without-building, the embedded graph will be detected and used automatically. This provides significant performance benefits in CI environments where build and test stages run on separate runners, as the test stage no longer needs a full source checkout or project generation to determine which tests to run.

Benefits#

Tuist Selective Testing for Xcode projects significantly reduces CI and local test execution times by skipping unaffected tests. This leads to faster feedback cycles, improved developer productivity, and more efficient use of CI resources. The feature is particularly beneficial for large Xcode codebases with many tests and frequent changes.

For CI environments using a split build and test workflow, the optimized --without-building workflow provides additional performance improvements by eliminating project generation and hash computation overhead in the test stage, making parallel test execution substantially faster.

Limitations#

  • Target-level granularity: Tuist Selective Testing operates at the target level, not at the file or test case level. Changes within a target trigger all its tests, even if only a single file changed. This also applies to foreign build targets: any change to the foreign build script or its declared inputs (files, folders, globs, or scripts) will cause all dependent tests to be re-run.
  • Test coverage incompatibility: Coverage tools expect the entire test suite to run and may report inaccurate coverage when selective testing is enabled. Coverage data may not reflect reality in this context.
  • Cache limitations and false positives: Tuist Selective Testing only considers file changes, not the outcome of previous test runs. If a test previously failed or timed out, but no new source changes are detected, it may be skipped and now reported as 'skipped' (instead of 'passed'). This explicit status helps distinguish between tests that were executed and those that were skipped due to unchanged sources or a failed build. There is currently no configuration to force rerunning tests after a failed or incomplete run; you must disable selective testing or manually invalidate the cache.
  • Prerequisites: A Tuist account and project setup are required.
  • Explicit skipped status: When tests are skipped due to a failed build or because Tuist Selective Testing determines no changes, their status is now reported as 'skipped' in Tuist's test run reporting and CI feedback. This allows for more accurate tracking of which tests were actually executed versus skipped.

How to Enable and Use Selective Testing#

For Tuist-Generated Projects#

Tuist Selective Testing is available only for Xcode projects generated by Tuist. To use selective testing, run:

tuist test

This command provides additional features, such as integration with Tuist's binary cache to further speed up builds and test runs. For a complete list of all available flags and options for the tuist test command, see the CLI command reference.

Test Execution Visibility:

When you run tuist test, the CLI logs which targets will be tested and which targets are skipped by selective testing. This provides complete transparency into the test execution plan before tests run. For example:

Testing the following targets: CoreTests, FeatureATests
Skipping the following targets: FeatureBTests

This logging helps you understand exactly what tests are about to run and what's being skipped due to selective testing.

Additionally, test schemes include a post-action that runs tuist inspect test after test execution. This action automatically collects and displays test insights for your local builds, including test outcomes and relevant statistics. Skipped tests are explicitly reported with a 'skipped' status in the UI and test summaries, making it clear which tests were not executed due to Tuist Selective Testing or build failures. No manual configuration is required—this is integrated into the default test schemes generated by Tuist.

UI Tests#

For UI tests, you must specify the destination device or simulator in advance. For example:

tuist test --device 'iPhone 14 Pro'
# or
tuist test -- -destination 'name=iPhone 14 Pro'
# or
tuist test -- -destination 'id=SIMULATOR_ID'

Selective testing for UI tests only works when the destination is specified (source).

Continuous Integration and Git Platform Integration#

To get automatic pull/merge request comments about which tests were run or skipped, integrate your Tuist project with a Git platform (such as GitHub) and use Tuist Selective Testing in your CI workflow. Tuist will post comments directly in your pull/merge requests, detailing which tests ran, which were skipped, and their respective statuses ('passed', 'failed', or 'skipped'). For failed tests, the PR comments include specific details for each test (up to 5 tests shown), such as test names, module names, test suite names, failure messages, and links to both the test case page and the source file location in GitHub. Flaky tests are excluded from the failed tests section to avoid duplication. This explicit reporting helps teams quickly identify which tests were executed, which were skipped due to unchanged sources or build failures, and which failed with detailed diagnostics.

Disabling Selective Testing or Forcing Full Test Runs#

If you need to force a full test run (for example, after a failed or incomplete run), disable Tuist Selective Testing with the --no-selective-testing flag or manually invalidate the cache. This ensures all tests are executed regardless of detected changes.

Inspecting Selective Testing Results#

After running tests, you can inspect which targets were skipped (cache hit) or ran (cache miss) using the CLI command:

tuist test xcode target list <test-run-id>

This command displays per-target selective testing data, showing which targets ran and which were skipped, along with their selective testing hashes.

Filtering by Hit Status#

Use the --hit-status filter to focus on specific target groups:

  • miss: Targets that ran (cache miss)
  • local: Targets skipped via local cache hit
  • remote: Targets skipped via remote cache hit

For example:

tuist test xcode target list <test-run-id> --hit-status miss
tuist test xcode target list <test-run-id> --hit-status remote

Each target includes its name, hit_status, and selective testing hash.

Pagination and JSON Output#

For large test suites, use pagination options:

  • --page: Specify the page number (default: 1)
  • --page-size: Items per page (default: 20, max: 100)

For machine-readable output or integration with scripts, use the --json flag:

tuist test xcode target list <test-run-id> --hit-status miss --json

API Access to Selective Testing Data#

Selective testing target data is available programmatically via the API:

GET /api/projects/{account_handle}/{project_handle}/tests/{test_run_id}/targets

Query parameters:

  • hit_status (optional): Filter by miss, local, or remote
  • page (optional): Page number (default: 1)
  • page_size (optional): Items per page (default: 20, max: 100)

The response includes a targets array with name, hit_status, and hash for each target, plus pagination metadata. This enables programmatic analysis and integration with CI/CD pipelines.

Analyzing Selective Testing Effectiveness#

Tuist provides MCP (Model Context Protocol) tools for analyzing selective testing effectiveness:

  • list_xcode_selective_testing_targets: Provides per-target hit/miss status and selective testing hashes
  • analyze_selective_testing: Offers a guided workflow for diagnosing regressions in test selection effectiveness

These tools help identify why selective testing effectiveness dropped, such as due to environment changes (Xcode or macOS version upgrades), dependency changes, or cache invalidation. The analysis workflow includes comparing test runs, calculating effectiveness percentages (based on cache hit rates), and providing actionable recommendations.

Use these tools to investigate scenarios where test selection efficiency decreases, diagnose root causes (such as cascading dependency changes or CI environment modifications), and optimize your selective testing configuration.

Best Practices#

  • Keep targets small and focused to maximize the benefits of Tuist Selective Testing.
  • When using foreign build targets (e.g., Kotlin Multiplatform, Rust, CMake), ensure that all files, folders, globs, and scripts that affect the output are declared as inputs. This ensures that selective testing accurately tracks changes and only re-runs affected tests.
  • Use Tuist Selective Testing for routine CI runs to speed up feedback, but consider disabling it for critical releases or after failed builds.
  • Be aware of the limitations regarding test coverage and cache invalidation.
Selective Testing in Xcode Projects | Dosu