---
url: /guide/basic-commands/run-tests.md
---
# Run tests {#run-tests}

Xmake provides a built-in `xmake test` command for running unit tests and test cases. Starting from version 2.8.5, you can configure test cases through `add_tests` on targets that need testing, and then automatically execute all tests.

This provides great convenience for automated testing, and even if a target is set to `set_default(false)`, xmake will still automatically compile it when executing tests and then run all tests.

For running regular target programs, see [Run Targets](/guide/basic-commands/run-targets).

## Command format

```sh
$ xmake test [options] [target/testname]
```

## Basic usage

### Configure test cases

First, we need to configure test cases for the target using `add_tests`:

```lua
add_rules("mode.debug", "mode.release")

for _, file in ipairs(os.files("src/test_*.cpp")) do
    local name = path.basename(file)
    target(name)
        set_kind("binary")
        set_default(false)
        add_files("src/" .. name .. ".cpp")
        add_tests("default")
        add_tests("args", {runargs = {"foo", "bar"}})
        add_tests("pass_output", {trim_output = true, runargs = "foo", pass_outputs = "hello foo"})
        add_tests("fail_output", {fail_outputs = {"hello2 .*", "hello xmake"}})
end
```

:::tip Detailed API
For complete parameters and usage of `add_tests`, please refer to: [add\_tests API documentation](/api/description/project-target#add-tests)
:::

### Run all tests

Simply execute `xmake test` to run all configured test cases:

```sh
$ xmake test
running tests ...
[  2%]: test_1/args        .................................... passed 7.000s
[  5%]: test_1/default     .................................... passed 5.000s
[  8%]: test_1/fail_output .................................... passed 5.000s
[ 11%]: test_1/pass_output .................................... passed 6.000s
[ 13%]: test_2/args        .................................... passed 7.000s
[ 16%]: test_2/default     .................................... passed 6.000s
[ 19%]: test_2/fail_output .................................... passed 6.000s
[ 22%]: test_2/pass_output .................................... passed 6.000s
[ 25%]: test_3/args        .................................... passed 7.000s
[ 27%]: test_3/default     .................................... passed 7.000s
[ 30%]: test_3/fail_output .................................... passed 6.000s
[ 33%]: test_3/pass_output .................................... passed 6.000s
[ 36%]: test_4/args        .................................... passed 6.000s
[ 38%]: test_4/default     .................................... passed 6.000s
[ 41%]: test_4/fail_output .................................... passed 5.000s
[ 44%]: test_4/pass_output .................................... passed 6.000s
[ 47%]: test_5/args        .................................... passed 5.000s
[ 50%]: test_5/default     .................................... passed 6.000s
[ 52%]: test_5/fail_output .................................... failed 6.000s
[ 55%]: test_5/pass_output .................................... failed 5.000s
[ 58%]: test_6/args        .................................... passed 7.000s
[ 61%]: test_6/default     .................................... passed 6.000s
[ 63%]: test_6/fail_output .................................... passed 6.000s
[ 66%]: test_6/pass_output .................................... passed 6.000s
[ 69%]: test_7/args        .................................... failed 6.000s
[ 72%]: test_7/default     .................................... failed 7.000s
[ 75%]: test_7/fail_output .................................... failed 6.000s
[ 77%]: test_7/pass_output .................................... failed 5.000s
[ 80%]: test_8/args        .................................... passed 7.000s
[ 83%]: test_8/default     .................................... passed 6.000s
[ 86%]: test_8/fail_output .................................... passed 6.000s
[ 88%]: test_8/pass_output .................................... failed 5.000s
[ 91%]: test_9/args        .................................... passed 6.000s
[ 94%]: test_9/default     .................................... passed 6.000s
[ 97%]: test_9/fail_output .................................... passed 6.000s
[100%]: test_9/pass_output .................................... passed 6.000s

80% tests passed, 7 tests failed out of 36, spent 0.242s
```

### Run specific test targets

You can specify to run a specific test:

```sh
$ xmake test targetname/testname
```

Or run all tests of a target or batch tests by pattern matching:

```sh
$ xmake test targetname/*
$ xmake test targetname/foo*
```

You can also run tests with the same name for all targets:

```sh
$ xmake test */testname
```

## Test configuration options

`add_tests` supports the following configuration parameters:

| Parameter | Description |
|-----------|-------------|
| `runargs` | Test run argument string or array |
| `runenvs` | Test run environment variable table |
| `timeout` | Test timeout in seconds |
| `trim_output` | Whether to trim output whitespace |
| `pass_outputs` | Expected output patterns for test to pass |
| `fail_outputs` | Output patterns that should cause test to fail |
| `build_should_pass` | Test should build successfully |
| `build_should_fail` | Test should fail to build |
| `files` | Additional test files to compile |
| `defines` | Additional defines for test compilation |
| `realtime_output` | Show test output in real-time |

### Example configurations

#### Test with arguments

```lua
target("mytest")
    set_kind("binary")
    add_files("src/*.cpp")
    add_tests("with_args", {runargs = {"--verbose", "--mode=test"}})
```

:::tip Related APIs

* `set_runargs`: [Set target run arguments](/api/description/project-target#set-runargs)
* `set_rundir`: [Set run working directory](/api/description/project-target#set-rundir)
* `add_runenvs`: [Add run environment variables](/api/description/project-target#add-runenvs)
  :::

#### Test with expected output

```lua
target("mytest")
    set_kind("binary")
    add_files("src/*.cpp")
    add_tests("output_test", {pass_outputs = ".*success.*", trim_output = true})
```

#### Test with timeout

```lua
target("mytest")
    set_kind("binary")
    add_files("src/*.cpp")
    add_tests("timeout_test", {timeout = 5}) -- 5 seconds timeout
```

#### Test with environment variables

```lua
target("mytest")
    set_kind("binary")
    add_files("src/*.cpp")
    add_tests("env_test", {runenvs = {TEST_MODE = "1", DEBUG = "true"}})
```

:::tip Related APIs

* `add_runenvs`: [Add run environment variables](/api/description/project-target#add-runenvs)
* `set_runenv`: [Set run environment variable](/api/description/project-target#set-runenv)
  :::

#### Test build failure

```lua
target("compile_fail_test")
    set_kind("binary")
    add_files("src/invalid.cpp")
    add_tests("should_fail", {build_should_fail = true})
```

:::tip Related APIs

* `set_default`: [Set target default build](/api/description/project-target#set-default)
* `set_kind`: [Set target type](/api/description/project-target#set-kind)
  :::

#### Add extra code files

`add_tests` supports adding extra code files for compilation through the `files` parameter, which is very useful for unit testing:

```lua
target("mytest")
    set_kind("binary")
    add_files("src/*.cpp")
    add_tests("test_with_stub", {
        files = "tests/stub_*.cpp",  -- Add extra test files
        defines = "TEST_MODE",
        remove_files = "src/main.cpp"  -- Remove unwanted files
    })
```

:::tip Use cases

* **Unit testing**: Add test code files without modifying original source code
* **Stub code**: Provide mock implementations for testing
* **Conditional compilation**: Add test-specific macro definitions via `defines`
* **File replacement**: Remove unwanted files using `remove_files` (e.g., main.cpp)
  :::

Taking doctest as an example, you can externally unit test without modifying any main.cpp:

```lua
target("doctest")
    set_kind("binary")
    add_files("src/*.cpp")
    for _, testfile in ipairs(os.files("tests/*.cpp")) do
        add_tests(path.basename(testfile), {
            files = testfile,
            remove_files = "src/main.cpp",
            languages = "c++11",
            defines = "DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN"
        })
    end
end
```

## Integrating Third-party Testing Frameworks

`xmake test` can integrate well with third-party testing frameworks like doctest, gtest, etc., to achieve more powerful testing capabilities.

### doctest Integration Example

```lua
add_rules("mode.debug", "mode.release")
add_requires("doctest")

target("doctest")
    set_kind("binary")
    add_files("src/*.cpp")
    for _, testfile in ipairs(os.files("tests/*.cpp")) do
        add_tests(path.basename(testfile), {
            files = testfile,
            remove_files = "src/main.cpp",
            languages = "c++11",
            packages = "doctest",  -- Integrate doctest package
            defines = "DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN"
        })
    end
```

Test file example (`tests/test_1.cpp`):

```cpp
#include "doctest/doctest.h"

static int factorial(int number) {
    return number <= 1 ? number : factorial(number - 1) * number;
}

TEST_CASE("testing the factorial function") {
    CHECK(factorial(1) == 1);
    CHECK(factorial(2) == 2);
    CHECK(factorial(3) == 6);
    CHECK(factorial(10) == 3628800);
}
```

### gtest Integration Example

```lua
add_rules("mode.debug", "mode.release")
add_requires("gtest")

target("mytest")
    set_kind("binary")
    add_files("src/*.cpp")
    for _, testfile in ipairs(os.files("tests/*.cpp")) do
        add_tests(path.basename(testfile), {
            files = testfile,
            remove_files = "src/main.cpp",
            packages = "gtest",  -- Integrate gtest package
            defines = "TEST_MAIN"
        })
    end
```

:::tip Framework Advantages

* **doctest**: Lightweight, header-only, easy to integrate
* **gtest**: Feature-rich, maintained by Google, active community
* **Catch2**: Modern C++ style, powerful features
  :::

### Dynamic Library Testing

You can also test dynamic libraries:

````lua
target("mylib")
    set_kind("shared")
    add_files("src/*.cpp")

target("mylib_test")
    set_kind("binary")
    add_deps("mylib")
    add_files("tests/*.cpp")
    add_packages("gtest")
    add_tests("default")

## Verbose output

Use `-v` to get verbose output:

```sh
$ xmake test -v
````

Use `-vD` to view detailed test failure error messages:

```sh
$ xmake test -vD
```

The `-vD` parameter displays more detailed log output, including output file paths and error information for each test:

```sh
report of tests:
[  2%]: test_10/compile_fail .... passed 0.001s
[  4%]: test_11/compile_pass .... failed 0.001s
errors: build/.gens/test_11/macosx/x86_64/release/tests/test_11/compile_pass.errors.log
[  7%]: test_1/args ............. passed 0.045s
stdout: build/.gens/test_1/macosx/x86_64/release/tests/test_1/args.stdout.log
[  9%]: test_1/default .......... passed 0.046s
stdout: build/.gens/test_1/macosx/x86_64/release/tests/test_1/default.stdout.log
[ 11%]: test_1/fail_output ...... passed 0.046s
stdout: build/.gens/test_1/macosx/x86_64/release/tests/test_1/fail_output.stdout.log
[ 14%]: test_1/pass_output ...... passed 0.047s
stdout: build/.gens/test_1/macosx/x86_64/release/tests/test_1/pass_output.stdout.log
...
[100%]: test_timeout/run_timeout  failed 1.007s
errors: build/.gens/test_timeout/macosx/x86_64/release/tests/test_timeout/run_timeout.errors.log

Detailed summary:
Failed tests:
 - test_11/compile_pass
 - test_5/fail_output
 - test_5/pass_output
 - test_7/args
 - test_7/default
 - test_7/fail_output
 - test_7/pass_output
 - test_8/pass_output
 - test_timeout/run_timeout

78% tests passed, 9 test(s) failed out of 42, spent 1.212s
```

### Log file description

When using `-vD`, xmake generates the following log files:

* **stdout logs**: `build/.gens/{target}/tests/{testname}.stdout.log` - Standard output of tests
* **errors logs**: `build/.gens/{target}/tests/{testname}.errors.log` - Error output of tests
* **build error logs**: Detailed error information when compilation fails

These log files are very useful for debugging test failure issues, allowing you to view specific output content and error information.

## Group testing

You can group tests using pattern matching:

```sh
# Run all tests starting with "unit_"
$ xmake test */unit_*

# Run all tests for targets starting with "test_"
$ xmake test test_*/*

# Run specific test across all targets
$ xmake test */default
```

## Continuous integration

For CI/CD environments, you can use the following patterns:

```sh
# Run tests and exit with error code on failure
$ xmake test

# Run tests with detailed output for CI logs
$ xmake test -v

# Run specific test groups
$ xmake test unit_tests/*
$ xmake test integration_tests/*
```

The test command will return a non-zero exit code if any tests fail, making it suitable for CI pipelines.
