# 4.8 并行测试

**NOTE**:*此示例代码可以在* <https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-04/recipe-08> *中找到。该示例在CMake 3.5版(或更高版本)中是有效的，并且已经在GNU/Linux、macOS和Windows上进行过测试。*

大多数现代计算机都有4个或更多个CPU核芯。CTest有个非常棒的特性，能够并行运行测试，如果您有多个可用的核。这可以减少测试的总时间，而减少总测试时间才是真正重要的，从而开发人员频繁地进行测试。本示例中，我们将演示这个特性，并讨论如何优化测试以获得最大的性能。

其他测试可以进行相应地表示，我们把这些测试脚本放在`CMakeLists.txt`同目录下面的test目录中。

## 准备工作

我们假设测试集包含标记为a, b，…，j的测试用例，每一个都有特定的持续时间:

| 测试用例       | 该单元的耗时 |
| ---------- | ------ |
| a, b, c, d | 0.5    |
| e, f, g    | 1.5    |
| h          | 2.5    |
| i          | 3.5    |
| j          | 4.5    |

时间单位可以是分钟，但是为了保持简单和简短，我们将使用秒。为简单起见，我们可以用Python脚本表示`test a`，它消耗0.5个时间单位:

```python
import sys
import time

# wait for 0.5 seconds
time.sleep(0.5)

# finally report success
sys.exit(0)
```

其他测试同理。我们将把这些脚本放在`CMakeLists.txt`下面，一个名为`test`的目录中。

## 具体实施

对于这个示例，我们需要声明一个测试列表，如下:

1. `CMakeLists.txt`非常简单：

   ```
   # set minimum cmake version
   cmake_minimum_required(VERSION 3.5 FATAL_ERROR)

   # project name
   project(recipe-08 LANGUAGES NONE)

   # detect python
   find_package(PythonInterp REQUIRED)

   # define tests
   enable_testing()
   add_test(a ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/a.py)
   add_test(b ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/b.py)
   add_test(c ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/c.py)
   add_test(d ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/d.py)
   add_test(e ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/e.py)
   add_test(f ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/f.py)
   add_test(g ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/g.py)
   add_test(h ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/h.py)
   add_test(i ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/i.py)
   add_test(j ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/j.py)
   ```
2. 我们可以配置项目，使用`ctest`运行测试，总共需要17秒:

   ```
   $ mkdir -p build
   $ cd build
   $ cmake ..
   $ ctest

   Start 1: a
   1/10 Test #1: a ................................ Passed 0.51 sec
   Start 2: b
   2/10 Test #2: b ................................ Passed 0.51 sec
   Start 3: c
   3/10 Test #3: c ................................ Passed 0.51 sec
   Start 4: d
   4/10 Test #4: d ................................ Passed 0.51 sec
   Start 5: e
   5/10 Test #5: e ................................ Passed 1.51 sec
   Start 6: f
   6/10 Test #6: f ................................ Passed 1.51 sec
   Start 7: g
   7/10 Test #7: g ................................ Passed 1.51 sec
   Start 8: h
   8/10 Test #8: h ................................ Passed 2.51 sec
   Start 9: i
   9/10 Test #9: i ................................ Passed 3.51 sec
   Start 10: j
   10/10 Test #10: j ................................ Passed 4.51 sec
   100% tests passed, 0 tests failed out of 10
   Total Test time (real) = 17.11 sec
   ```
3. 现在，如果机器有4个内核可用，我们可以在不到5秒的时间内在4个内核上运行测试集:

   ```
   $ ctest --parallel 4

   Start 10: j
   Start 9: i
   Start 8: h
   Start 5: e
   1/10 Test #5: e ................................ Passed 1.51 sec
   Start 7: g
   2/10 Test #8: h ................................ Passed 2.51 sec
   Start 6: f
   3/10 Test #7: g ................................ Passed 1.51 sec
   Start 3: c
   4/10 Test #9: i ................................ Passed 3.63 sec
   5/10 Test #3: c ................................ Passed 0.60 sec
   Start 2: b
   Start 4: d
   6/10 Test #6: f ................................ Passed 1.51 sec
   7/10 Test #4: d ................................ Passed 0.59 sec
   8/10 Test #2: b ................................ Passed 0.59 sec
   Start 1: a
   9/10 Test #10: j ................................ Passed 4.51 sec
   10/10 Test #1: a ................................ Passed 0.51 sec
   100% tests passed, 0 tests failed out of 10
   Total Test time (real) = 4.74 sec
   ```

## 工作原理

可以观察到，在并行情况下，测试j、i、h和e同时开始。当并行运行时，总测试时间会有显著的减少。观察`ctest --parallel 4`的输出，我们可以看到并行测试运行从最长的测试开始，最后运行最短的测试。从最长的测试开始是一个非常好的策略。这就像打包移动的盒子：从较大的项目开始，然后用较小的项目填补空白。a-j测试在4个核上的叠加比较，从最长的开始，如下图所示:

```
--> time
core 1: jjjjjjjjj
core 2: iiiiiiibd
core 3: hhhhhggg
core 4: eeefffac
```

按照定义测试的顺序运行，运行结果如下:

```
--> time
core 1: aeeeiiiiiii
core 2: bfffjjjjjjjjj
core 3: cggg
core 4: dhhhhh
```

按照定义测试的顺序运行测试，总的来说需要更多的时间，因为这会让2个核大部分时间处于空闲状态(这里的核3和核4)。CMake知道每个测试的时间成本，是因为我们先顺序运行了测试，将每个测试的成本数据记录在`test/Temporary/CTestCostData.txt`文件中:

```
a 1 0.506776
b 1 0.507882
c 1 0.508175
d 1 0.504618
e 1 1.51006
f 1 1.50975
g 1 1.50648
h 1 2.51032
i 1 3.50475
j 1 4.51111
```

如果在配置项目之后立即开始并行测试，它将按照定义测试的顺序运行测试，在4个核上的总测试时间明显会更长。这意味着什么呢？这意味着，我们应该减少的时间成本来安排测试？这是一种决策，但事实证明还有另一种方法，我们可以自己表示每次测试的时间成本:

```
add_test(a ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/a.py)
add_test(b ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/b.py)
add_test(c ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/c.py)
add_test(d ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/d.py)
set_tests_properties(a b c d PROPERTIES COST 0.5)

add_test(e ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/e.py)
add_test(f ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/f.py)
add_test(g ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/g.py)
set_tests_properties(e f g PROPERTIES COST 1.5)

add_test(h ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/h.py)
set_tests_properties(h PROPERTIES COST 2.5)

add_test(i ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/i.py)
set_tests_properties(i PROPERTIES COST 3.5)

add_test(j ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/j.py)
set_tests_properties(j PROPERTIES COST 4.5)
```

成本参数可以是一个估计值，也可以从`test/Temporary/CTestCostData.txt`中提取。

## 更多信息

除了使用`ctest --parallel N`，还可以使用环境变量`CTEST_PARALLEL_LEVEL`将其设置为所需的级别。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chenxiaowei.gitbook.io/cmake-cookbook/4.0-chinese/4.8-chinese.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
