这篇开始学习单元测试示范,在GTest这个下载包,里面有一个samples文件夹,里面有10个单元测试例子,告诉我们怎么去使用googletest这个测试框架。我认为,这种官方文档,是十分有必要认真,一个一个学习完成,你才可以说基本会用GTest这个框架。我们先学习会用,高级的原理层我们等有基础再尝试去看看源码学习学习。
1.第一个单元测试:n的阶乘和n是否素数两个函数的单元测试
一共三个文件,一个头文件,一个实现文件,一个是单元测试文件。典型模拟我们真实项目中的情况。
2.相关代码拷贝到vs2015
我把这三个文件,拷贝到了vs2015环境,然后把注释用中文写了一遍
第一个头文件sample01.h 代码如下
// 使用Google C++ testing 框架的一个简单的编程示例
#ifndef GTEST_SAMPLES_SAMPLE01_H_
#define GTEST_SAMPLES_SAMPLE01_H_
// 返回n的阶乘,如果n为负数,返回1
int Factorial(int n);
// 如果是素数返回true,否则返回false
bool IsPrime(int n);
#endif // GTEST_SAMPLES_SAMPLE01_H_
实现类cpp文件
// 使用Google C++ testing 框架的一个简单的编程示例
#include "sample01.h"
// 返回n的阶乘,如果n为负数,返回1
int Factorial(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
// 如果是素数返回true,否则返回false
bool IsPrime(int n) {
// 分支1: n小于等于1的数
if (n <= 1) return false;
// 分支2: 偶数
if (n % 2 == 0) return n == 2;
// 接下来n为奇数,且n>=3
// i从3开始,尝试用n去除奇数i
for (int i = 3; ; i += 2) {
// 我们只需要尝试i直到n的平方根
if (i > n / i) break;
// 现在 i <= n/i < n.
// 如果n能被i整除,n不是素数
if (n % i == 0) return false;
}
// n 在范围 (1, n)没有整数因子,所以n是素数
return true;
}
上面两个函数,第一个求n的阶乘。第二个是判断一个数是不是素数。具体什么是素数可以先百度一下,这种函数内部分支覆盖的白盒测试是需要完整理解函数内部的逻辑。
单元测试类代码
// 使用 1-2-3步骤,很容易使用Gtest框架编写单元测试
// 步骤1. 根据你的实际情况,包含所需的头文件
// 不要忘记包含 gtest.h, 采用使用GTest测试框架
#include <limits.h>
#include "sample01.h"
#include "gtest/gtest.h"
namespace {
// 步骤2. 使用TEST宏去定义你的测试
//
// TEST 宏有两个参数: 测试用例名称 和测试名称.
// 使用TEST宏之后, 你需要在{}之内定义你的测试逻辑。你可以使用一堆宏来断言成功还是失败. EXPECT_TRUE 和 EXPECT_EQ
// 是这堆断言宏的两个常用的宏. 可以打开gtest.h阅读找到全部的断言宏。
//
//在GTest中,测试被划分为不同的测试用例. 这样组织用例,保证了测试代码的井井有条。
//你应该把逻辑相关的测试放入奥相同的测试用例中。
//
// 测试名称和测试用例名称都需要符合C++标识符规范
// 并且在测试名称和测试用例名称中,你不能使用下划线(_)
// Google Test 框架可以确保每个测试都执行一次,但不确保测试顺序执行。
//所以,你要确保你写的测试的结果不依赖其他测试的顺序
// 测试函数 Factorial().
// 使用负数测试斐波那契数列
TEST(FactorialTest, Negative) {
// This test is named "Negative", and belongs to the "FactorialTest"
// 测试用例
EXPECT_EQ(1, Factorial(-5));
EXPECT_EQ(1, Factorial(-1));
EXPECT_GT(Factorial(-10), 0);
// EXPECT_EQ(expected, actual) 等价于 EXPECT_TRUE((expected) == (actual))
// 当断言发生失败,EXPECT 相关宏会同时打印实际结果和预期结果
// 这在调试的时候非常有用
// 因此在这种情况下首选 EXPECT_EQ宏用来断言
//
// 另一边,EXPECT_TRUE 宏接受一切的布尔表达式,更为笼统。
}
// 测试0的阶乘
TEST(FactorialTest, Zero) {
EXPECT_EQ(1, Factorial(0));
}
// 测试正整数的阶乘
TEST(FactorialTest, Positive) {
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(6, Factorial(3));
EXPECT_EQ(40320, Factorial(8));
}
// 以下测试函数 IsPrime()
// 测试负数输出
TEST(IsPrimeTest, Negative) {
// 这个测试点属于 IsPrimeTest测试用例
EXPECT_FALSE(IsPrime(-1));
EXPECT_FALSE(IsPrime(-2));
EXPECT_FALSE(IsPrime(INT_MIN));
}
// 测试一些简单的数字n=0,1,2,3
TEST(IsPrimeTest, Trivial) {
EXPECT_FALSE(IsPrime(0));
EXPECT_FALSE(IsPrime(1));
EXPECT_TRUE(IsPrime(2));
EXPECT_TRUE(IsPrime(3));
}
// 测试一些正整数n
TEST(IsPrimeTest, Positive) {
EXPECT_FALSE(IsPrime(4));
EXPECT_TRUE(IsPrime(5));
EXPECT_FALSE(IsPrime(6));
EXPECT_TRUE(IsPrime(23));
}
} // namespace
// 步骤3. 在 main()调用 RUN_ALL_TESTS() .
// 这个我们写在了LearnGtest.cpp文件中的main函数
保存之后,点击 生成->生成解决方案,然后 调试->开始执行(不调试),观察测试结果输出报告
上面具体素数的逻辑,需要好好想以下。
3.总结
3.1 上面说的编写Gtest用例采用 1-2-3 三步法,其实就两部:
步骤1:引入头文件,包括被测试对象头文件和gtest.h这个头文件
步骤2:使用TEST宏,有两个参数,第一个是测试用例名称,第二个是测试点名称
文档中说得步骤3是我们在别的文件的main()函数中调用了RUN_ALL_TESTS()方法。
3.2 Gtest中提供了一堆断言用的宏,可以在gtest.h文件找到更多宏
3.3 不需要我们手动把用例注册到Gtest框架,这个框架会自动发现TEST宏的测试用例。
3.4 每个TEST宏的测试会被执行,但是执行不确保用例的顺序
3.5 当前在断言,建议使用EXPECT开头的宏,因为断言出错之后,会打印实际结果和预期结果,方便调试问题
4.其他练习
可以修改断言语句中一个值,让其中一条用例执行失败,看看控制台打印失败的信息能不能根据失败日志,找到定位出问题的代码行,并修复,使测试成功运行通过。