前面我们写过一篇关于Kunit怎么快速使用起来的文章,但是当时只是搭建了框架,让整个KUNIT跑起来了。使用到的关于KUNIT中的东西还是比较的少。现在这次我们去测试一些复杂的场景,使用到一些复杂的断言。继续我们的二点点KUNIT,学习一下KUNIT中的东西。
1、测试用例
在KUNIT中最基本的单元就是测试用例,一个测试用例就是一个函数:void (*)(struct kunit *test)。在这个函数下面调用了需要测试的接口。比如:
void example_test_success(struct kunit *test)
{
}
void example_test_failure(struct kunit *test)
{
KUNIT_FAIL(test, "This test never passes.");
}
在上面的代码中,example_test_success 始终会通过,因为这个函数实际上什么都没做。而下面的example_test_failure始终会失败,因为这个调用了KUNIT_FAIL这个特殊的断言,这个会打印失败信息,并让这个用例测试不通过。
2、期望-KUNIT_EXPECT_EQ
这个期望就是指我们期望一段代码的运行结果应该是什么样子的。这个期望被称为函数。测试是通过设置对被测试代码行为的期望来进行的。
当一个或多个预期失败时,测试函数的结果与我期望的不相符合的时候,测试用例失败,并记录有关失败的信息。例如:
void add_test_basic(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, 1, add(1, 0));
KUNIT_EXPECT_EQ(test, 2, add(1, 1));
}
在上面的示例中,add_test_basic对一个名为add的函数的行为进行了许多断言。第一个参数始终是struct kunit*类型,其中包含有关当前测试上下文的信息。
在本例中,第二个参数是期望的值。最后一个值是实际值。如果add通过了所有这些期望,测试用例add_test_basic将通过;如果这些期望中的任何一个失败,测试用例将失败。
当违反任何期望时,测试用例失败;然而,测试将继续运行,并尝试其他预期,直到测试用例结束或终止。这与稍后讨论的断言相反。
例如,如果我们想要严格测试上面的add函数,请创建额外的测试用例,测试add函数应该具有的每个属性,如下所示:
{
KUNIT_EXPECT_EQ(test, 1, add(1, 0));
KUNIT_EXPECT_EQ(test, 2, add(1, 1));
}
void add_test_negative(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, 0, add(-1, 1));
}
void add_test_max(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX));
KUNIT_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN));
}
void add_test_overflow(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, INT_MIN, add(INT_MAX, 1));
}
就是要让测试用例越简单越好。
3、断言-Assertions
断言类似于预期,只是如果条件不满足,断言会立即终止测试用例。例如:
static void test_sort(struct kunit *test)
{
int *a, i, r = 1;
a = kunit_kmalloc_array(test, TEST_LEN, sizeof(*a), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, a);
for (i = 0; i < TEST_LEN; i++) {
r = (r * 725861) % 6599;
a[i] = r;
}
sort(a, TEST_LEN, sizeof(*a), cmpint, NULL);
for (i = 0; i < TEST_LEN-1; i++)
KUNIT_EXPECT_LE(test, a[i], a[i + 1]);
}
在这个例子中,被测试的方法应该返回一个值的指针。如果指针返回null或errno,我们希望停止测试,因为以下预期可能会导致测试用例崩溃。**ASSERT_NOT_ERR_OR_NULL(…)**允许我们在不满足完成测试的适当条件时退出测试用例。(见名知意)
4、测试套件
我们需要很多测试用例来覆盖单元的所有行为。有很多类似的测试是很常见的。为了减少这些密切相关的测试中的重复,大多数单元测试框架(包括KUnit)都提供了测试套件的概念。
测试套件是一个代码单元的测试用例集合,具有可选的设置和拆卸功能,在整个套件和/或每个测试用例之前/之后运行。例如:
static struct kunit_case example_test_cases[] = {
KUNIT_CASE(example_test_foo),
KUNIT_CASE(example_test_bar),
KUNIT_CASE(example_test_baz),
{}
};
static struct kunit_suite example_test_suite = {
.name = "example",
.init = example_test_init,
.exit = example_test_exit,
.suite_init = example_suite_init,
.suite_exit = example_suite_exit,
.test_cases = example_test_cases,
};
kunit_test_suite(example_test_suite);
在上面的示例中,测试套件example_test_suite将首先运行example_suite_init,然后运行测试用例example_test_foo、example_test_bar和example_test_baz。每个函数都会在其前面立即调用example_test_init,并在其后面立即调用example_test_exit。最后,example_suite_exit将在所有其他操作之后调用。kunit_test_suite(example_test_ssuite)向kunit测试框架注册测试套件。
这个就是通过这个测试套件,将一些需要初始化和退出的测试用例,给准备了环境。
测试用例只有在与测试套件关联时才会运行。kunit_test_suite(…)是一个宏,它告诉链接器将指定的测试套件放在一个特殊的链接器部分中,以便kunit可以在late_init之后运行,或者在加载测试模块时运行(如果测试是作为模块构建的)。