Google C++单元测试框架GoogleTest---值参数化测试
值参数化测试允许您使用不同的参数测试代码,而无需编写同一测试的多个副本。
假设您为代码编写测试,然后意识到您的代码受到布尔参数的影响。
TEST(MyCodeTest, TestFoo) { // A code to test foo(). }
通常人们在这种情况下将他们的测试代码考虑为具有布尔参数的函数。 该函数设置标志,然后执行测试代码:
void TestFooHelper(bool flag_value) { flag = flag_value; // A code to test foo(). } TEST(MyCodeTest, TestFoo) { TestFooHelper(false); TestFooHelper(true); }
但这种设置有严重的缺点。 首先,当测试断言在测试中失败时,不清楚参数的什么值导致它失败。 您可以将澄清消息流式传输到EXPECT / ASSERT语句中,但是您必须对所有这些语句进行。 第二,你必须为每个测试添加一个这样的帮助函数。 如果你有十个测试怎么办? 二十? 一百?
值参数化测试将允许您只写一次测试,然后轻松实例化并使用任意数量的参数值运行它。
一、如何写值参数化测试
1. 要写值参数化测试,首先应该定义一个fixture类。 它必须继承:: testing :: Test和:: testing :: WithParamInterface <T>(后者是纯粹的接口),其中T是参数值的类型。
为了方便,你可以从:: testing :: TestWithParam <T>派生fixture类,它本身是从:: testing :: Test和:: testing :: WithParamInterface <T>派生的。 T可以是任何可复制类型。 如果它是一个原始指针,你负责管理指向的值的生命周期。
class FooTest : public ::testing::TestWithParam<const char*> { // You can implement all the usual fixture class members here. // To access the test parameter, call GetParam() from class // TestWithParam<T>. }; // Or, when you want to add parameters to a pre-existing fixture class: class BaseTest : public ::testing::Test { ... }; class BarTest : public BaseTest, public ::testing::WithParamInterface<const char*> { ... };
2.告诉gtest你拿到参数的值后,具体做些什么样的测试
这里,我们要使用一个新的宏(嗯,挺兴奋的):TEST_P,关于这个"P"的含义,Google给出的答案非常幽默,就是说你可以理解为”parameterized" 或者 "pattern"。我更倾向于 ”parameterized"的解释,呵呵。在TEST_P宏里,使用GetParam()获取当前的参数的具体值。
TEST_P(FooTest, DoesBlah) { // Inside a test, access the test parameter with the GetParam() method // of the TestWithParam<T> class: //在测试中,使用TestWithParam <T>类的GetParam()方法访问测试参数: int n = GetParam(); EXPECT_TRUE(IsPrime(n)); ... } TEST_P(FooTest, HasBlahBlah) { ... }
3. 您可以使用INSTANTIATE_TEST_CASE_P来实例化具有任何您想要的参数的测试用例。 Google Test定义了一些用于生成测试参数的函数。 它们返回我们所谓的参数生成器(surprise!)。 这里是它们的摘要,它们都在testing命名空间中:
Range(begin, end[, step]) | Yields values {begin, begin+step, begin+step+step, ...} . The values do not include end . step defaults to 1. |
---|---|
Values(v1, v2, ..., vN) |
Yields values {v1, v2, ..., vN} . |
ValuesIn(container) and ValuesIn(begin, end) |
Yields values from a C-style array, an STL-style container, or an iterator range [begin, end) . container , begin , and end can be expressions whose values are determined at run time. |
Bool() |
Yields sequence {false, true} . |
Combine(g1, g2, ..., gN) |
这个比较强悍,它将g1,g2,...gN进行排列组合,g1,g2,...gN本身是一个参数生成器,每次分别从g1,g2,..gN中各取出一个值,组合成一个元组(Tuple)作为一个参数。 说明:这个功能只在提供了<tr1/tuple>头的系统中有效。gtest会自动去判断是否支持tr/tuple,如果你的系统确实支持,而gtest判断错误的话,你可以重新定义宏GTEST_HAS_TR1_TUPLE=1。See comments in include/gtest/internal/gtest-port.h for more information. |
有关更多详细信息,请参阅源代码中这些函数的定义中的注释。
以下语句将从FooTest测试用例中实例化测试,每个测试用参数值“meeny”,“miny”和“moe”。
INSTANTIATE_TEST_CASE_P(InstantiationName, FooTest, ::testing::Values("meeny", "miny", "moe"));
为了区分模式的不同实例(是的,您可以多次实例化),
INSTANTIATE_TEST_CASE_P的第一个参数是测试案例的前缀,可以任意取。
第二个参数是测试案例的名称,需要和之前定义的参数化的类的名称相同,如:IsPrimeParamTest
第三个参数是可以理解为参数生成器,上面的例子使用test::Values表示使用括号内的参数。Google提供了一系列的参数生成的函数:
请记住为不同的实例化选择唯一的前缀。 从上面的实例化的测试将有这些名称:
- InstantiationName / FooTest.DoesBlah / 0 for“meeny”
- InstantiationName / FooTest.DoesBlah / 1 for“miny”
- InstantiationName / FooTest.DoesBlah / 2 for“moe”
- InstantiationName / FooTest.HasBlahBlah / 0 for“meeny”
- InstantiationName / FooTest.HasBlahBlah / 1 for“miny”
- InstantiationName / FooTest.HasBlahBlah / 2 for“moe”
您可以在--gtest_filter.中使用这些名称。
请注意,INSTANTIATE_TEST_CASE_P将实例化给定测试用例中的所有测试,无论它们的定义是在INSTANTIATE_TEST_CASE_P语句之前还是之后。
You can see these files for more examples.
二、创建值参数化抽象测试
在上面,我们在同一个源文件中定义和实例化FooTest。有时您可能想在库中定义值参数化测试,并让其他人稍后实例化它们---这种模式称为抽象测试。
作为其应用程序的一个例子,当你设计一个接口时,你可以编写一个标准的抽象测试套件(也许使用一个工厂函数作为测试参数),该接口的所有实现都应该通过。当有人实现该接口时,他可以实例化您的套件以免费获得所有的接口一致性测试。
要定义抽象测试,你应该这样组织你的代码:
1.将参数化测试夹具类(例如FooTest)的定义放在头文件中,例如foo_param_test.h。这是你抽象测试的声明。
2.将TEST_P定义放在foo_param_test.cc中,其中include foo_param_test.h。这是你抽象测试的实现。
一旦定义它们,您可以通过包括foo_param_test.h,调用INSTANTIATE_TEST_CASE_P()和链接foo_param_test.cc来实例化它们。您可以多次实例化相同的抽象测试用例,可能在不同的源文件中。
三、一个简单的代码示例
//被测函数 bool IsPrime(int n) { // Trivial case 1: small numbers if (n <= 1) return false; // Trivial case 2: even numbers if (n % 2 == 0) return n == 2; // Now, we have that n is odd and n >= 3. // Try to divide n by every odd number i, starting from 3 for (int i = 3; ; i += 2) { // We only have to try i up to the squre root of n if (i > n / i) break; // Now, we have i <= n/i < n. // If n is divisible by i, n is not prime. if (n % i == 0) return false; } // n has no integer factor in the range (1, n), and thus is prime. return true; }
//第一步 class FooTest : public ::testing::TestWithParam<int> { // You can implement all the usual fixture class members here. // To access the test parameter, call GetParam() from class // TestWithParam<T>. //在这里面可以实现fixture类的所有成员 }; //第二步 TEST_P(FooTest, DoesBlah) { // Inside a test, access the test parameter with the GetParam() method // of the TestWithParam<T> class: //在测试中,使用TestWithParam <T>类的GetParam()方法访问测试参数: int n = GetParam(); EXPECT_TRUE(IsPrime(n)); //... } //第三步 //第一个参数是前缀;第二个是类名;第三个是参数生成器 INSTANTIATE_TEST_CASE_P(MyPrimeParamTest, FooTest, ::testing::Values(-5,0, 3, 5, 11)); int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
运行结果:
从上面的框框中的案例名称大概能够看出案例的命名规则,对于需要了解每个案例的名称的我来说,这非常重要。 命名规则大概为:
prefix/test_case_name.testname/index