最近我了解了GENERATE
Catch2 中的宏(来自此视频)。现在我很好奇它是如何在内部工作的。
天真地认为,对于带有生成器的测试用例k
(通过生成器,我的意思是一个GENERATE
调用站点),Catch2 只运行每个测试用例n1 * n2 * ... * nk
时间,其中ni
是i
第 -th 生成器中元素的数量,每次指定不同的值组合从那些k
发电机。事实上,这个幼稚的规范似乎适用于一个简单的测试用例:
TEST_CASE("Naive") {
auto x = GENERATE(0, 1);
auto y = GENERATE(2, 3);
std::cout << "x = " << x << ", y = " << y << std::endl;
}
正如预期的那样,输出是:
x = 0, y = 2
x = 0, y = 3
x = 1, y = 2
x = 1, y = 3
这表明测试用例运行了2 * 2 == 4
多次。
但是,catch 似乎并没有天真地实现它,如下例所示:
TEST_CASE("Depends on if") {
auto choice = GENERATE(0, 1);
int x = -1, y = -1;
if (choice == 0) {
x = GENERATE(2, 3);
} else {
y = GENERATE(4, 5);
}
std::cout << "choice = " << choice << ", x = " << x << ", y = " << y << std::endl;
}
在上述情况下,实际调用(不是调用点)GENERATE
取决于choice
. 如果逻辑实现得天真,人们会期望有 8 行输出(因为2 * 2 * 2 == 8
):
choice = 0, x = 2, y = -1
choice = 0, x = 2, y = -1
choice = 0, x = 3, y = -1
choice = 0, x = 3, y = -1
choice = 1, x = -1, y = 4
choice = 1, x = -1, y = 4
choice = 1, x = -1, y = 5
choice = 1, x = -1, y = 5
请注意重复的行:即使未实际调用生成器,天真的置换仍然会置换生成器的值。例如,y = GENERATE(4, 5)
仅在 时调用choice == 1
,但是,即使在 时choice != 1
,实现仍会置换值 4 和 5,即使未使用这些值。
但是,实际输出是:
choice = 0, x = 2, y = -1
choice = 0, x = 3, y = -1
choice = 1, x = -1, y = 4
choice = 1, x = -1, y = 5
没有重复的行。这让我怀疑 Catch 在内部使用堆栈来跟踪调用的生成器及其最近调用的顺序。每次测试用例完成一次迭代时,它会以相反的顺序遍历调用的生成器,并推进每个生成器的值。如果这种推进失败(即生成器内的值序列完成),则该生成器将重置为其初始状态(即准备好按顺序发出第一个值);否则(推进成功),遍历退出。
在伪代码中,它看起来像:
for each generator that is invoked in reverse order of latest invocation:
bool success = generator.moveNext();
if success: break;
generator.reset();
这完美地解释了前面的案例。但它并没有解释这个(相当模糊的)一个:
TEST_CASE("Non structured generators") {
int x = -1, y = -1;
for (int i = 0; i <= 1; ++i) {
x = GENERATE(0, 1);
if (i == 1) break;
y = GENERATE(2, 3);
}
std::cout << x << "," << y << std::endl;
}
人们会期望它运行4 == 2 * 2
时间,并且输出是:
x = 0, y = 2
x = 1, y = 2
x = 0, y = 3
x = 1, y = 3
(x
之前的更改y
是x = GENERATE(0, 1)
最后一次调用的生成器)
然而,这不是 catch实际所做的,这是现实中发生的:
x = 0, y = 2
x = 1, y = 2
x = 0, y = 3
x = 1, y = 3
x = 0, y = 2
x = 1, y = 2
x = 0, y = 3
x = 1, y = 3
8行输出,也就是前四行重复了两次。
所以我的问题是,GENERATE
Catch2 究竟是如何实现的?我并不是特别在寻找详细的代码,而是一个可以解释我在前面的示例中看到的内容的高级描述。