单元测试

项目管理和技术管理中做单元测试,衡量一个软件是否正常的标准,良好的单元测试以及足够多的覆盖率,至少保证关键功能,关键业务的覆盖率接近100%。

gtest是谷歌公司发布的一个跨平台(Linux、Mac OS、Windows等)的C++单元测试框架,它提供了丰富的断言、致命和非致命判断、参数化、死亡测试等等。

  • 两种断言:
  • ASSERT_*:当断言失败时,产生致命错误、并终止当前函数。
  • EXPECT_*:当断言失败时,产生非致命错误,并且不会终止当前函数。
  • 通常都会用EXPECT_*,因为能在一次测试中测试出更多的失败情况。
  • 如果要在出现失败测试时立即终止程序,则要选择ASSERT_*
  • ==注意:==因为ASSERT_*会在失败时立即终止函数,那么就可能跳过后面程序中进行清理工作的代码,可能会产生内存泄露。

测试stack

main函数在gtest_main.cc中,可以把这个main函数复制到你的代码里面,你将拥有它的控制权。

GTEST_API_ int main(int argc, char **argv) {
    std::cout << "Running main() from gtest_main.cc\n";
    
    testing::InitGoogleTest(&argc, argv);//解析命令行中的GoogleTest参数,它允许用户通过多样的命令行参数来控制测试程序的行为(即定制命令行参数的行为)
    return RUN_ALL_TESTS();//将会搜索不同的Test Case和不同的源文件中所有已经存在测试案例,然后运行它们,所有Test都成功时返回1,否则返回0。
}
  • 非致命断言,即使宏判断失败,程序仍然会顺序执行后面的代码
    每一个测试实例都必须初初始化每一个对象
    ==TEST()宏==的第一个参数是Test Case的名称,第二个参数是隶属于第一个Test Case参数的Test的名称。GoogleTest根据Test Case对测试结果进行分组,所以一些相关的Test应该放在同一个Test Case中。
#include "gtest/gtest.h"
#include "MyStack.h"

//测试实例1
TEST(testStack, simpletest) {
    MyStack st;
    st.push(4);
    EXPECT_EQ(4, st.pop());//使用Google Test宏进行测试(非致命断言)
}

//测试实例2
TEST(testStack, testAll) {
    MyStack st;
    st.push(9);
    st.push(28);
    
    int val = st.pop();
    EXPECT_EQ(28, val);//28等于val则测试通过(非致命断言)
    EXPECT_NE(0,val);//0不等于val则测试通过(非致命断言)
    EXPECT_GT(29, val);//29大于val则测试通过(非致命断言)
    EXPECT_GE(29, val);//29大于等于val则测试通过(非致命断言)
    EXPECT_TRUE(val == 28) << "val is not equal to 28";//val == 28结果为false,输出后面日志(非致命断言)
}
  • 致命断言,如果断言失败,当前函数剩下来的代码将会被跳过
TEST(testStack, testAll) {
    MyStack st;
    st.push(9);
    st.push(28);
    
    int val = st.pop();
    ASSERT_EQ(28, val);//28等于val则测试通过(致命断言)
    ASSERT_NE(0,val);//0不等于val则测试通过(致命断言)
    ASSERT_GT(29, val);//29大于val则测试通过(致命断言)
    ASSERT_TRUE(val == 28) << "val is not equal to 28";//val == 28结果为false,输出后面日志(致命断言)
}
  • 字符串断言
TEST(testStack, testAll) {
    MyStack st;
    st.push(9);
    st.push(28);
    
    int val = st.pop();
	
    EXPECT_EQ("9", st.toString().c_str());//字符串断言
}
  • 浮点数断言
TEST(testStack, testAll) {
    MyStack st;
    st.push(9);
    st.push(28);
    
    int val = st.pop();
	
    EXPECT_FLOAT_EQ(7.0, ((float)val)/4);
    EXPECT_DOUBLE_EQ(7.0, ((double)val)/4);
}
  • 测试装置(Test Fixtures)
    假如我们有非常多的测试实例,不想重复初始化
    TEST_F()宏,我们想让多个Test使用同一套数据配置时,就需要用到测试装置,创建测试装置的具体方法如下:
  • 派生一个继承 ::testing::Test 的类,并将该类中的一些内容声明为 protected 类型,以便在子类中进行访问;
  • 根据实际情况,编写默认的构造函数或SetUp()函数,来为每个 test 准备所需内容;
  • 根据实际情况,编写默认的析构函数或TearDown()函数,来释放SetUp()中分配的资源;
  • 根据实际情况,定义 test 共享的子程序。

不同用例之间,数据实际都是独占的,不会相互影响。

TEST_F()宏的第一个参数(即Test Case的名称)必须是测试装置类的类名。

对于TEST_F()定义的每个Test,Google Test将会在运行时创建一个新的测试装置对象,并立即通过SetUp()对其进行初始化,然后运行这个Test,之后通过TearDown()进行数据清理,最后删除测试装置对象。==同一个Test Case中不同的test具有不同的测试装置对象,并且Google Test每次创建新的测试装置前都会先删除之前的测试装置。==多个Test不会重用相同的测试装置,某个Test对测试装置进行修改对其他Test无影响。

#include "gtest/gtest.h"
#include "MyStack.h"

//MyStackTest继承于testing命名空间下的Test类
class MyStackTest : public testing :: Test {
protected:
	//总是在测试用例开始时被调用
    virtual void SetUp() {
		st.push(34);
		st.push(28);
		st.push(56);
	}
    
    //总是在测试用例结束后被调用
    virtual void TearDown() {
        
    }
    
    //构造函数,也可以做一部分SetUp函数的工作
    MyStackTest() {
        std::cout << "MyStackTest is constructed." << std::endl;
        st.push(22);
    }
    //析构函数,也可以做一部分TearDown函数的工作
    ~MyStackTest() {
        std::cout << "Destructed MyStackTest." << std::endl;
    }
    
    MyStack st;
};

//如果使用了测试装置,就必须使用TEST_F(测试装置类, 自定义名称)
TEST_F(MyStackTest, testPop) {
    //在这里会自动构造一个MyStack的实例,并调用SetUp函数
    int val = st.pop();
    EXPECT_EQ(56, val);
    //在这里会调用TearDown函数
}

//testPop2和testPop来自同一个测试装置,但是来自不同的测试装置实例,两个实例是相互独立的个体
TEST_F(MyStackTest, testPop2) {
    //在这里会自动构造一个MyStack的实例,并调用SetUp函数
    int val = st.pop();
    EXPECT_EQ(56, val);
    //在这里会调用TearDown函数
}
  • 全局事件
    要实现全局事件,必须写一个类,继承testing::Environment类,==定义一个该全局环境的一个对象并将该对象添加到全局环境测试中去。==实现里面的SetUpTearDown成员函数。SetUp在所有案例执行前执行,TearDown在所有案例执行后执行。
#include "gtest/gtest.h"

class GlobalTest : public testing::Environment {
public:
    void SetUp() {
        std::cout << "SetUp" << std::endl;
    }
    void TearDown() {
        std::cout << "TearDown" << std::endl;
    }
};

TEST(abs_test, test_1) {
    EXPECT_EQ(std::abs(-1), 1);
}

TEST(abs_test, test_2) {
    EXPECT_EQ(std::abs(0), 0);
}

int main(int argc, char **argv) {
    std::cout << "Running main() from gtest_main.cc\n";
    
    testing::InitGoogleTest(&argc, argv);
    testing::Environment* env = new GlobalTest();
    testing::AddGlobalTestEnvironment(env);
    return RUN_ALL_TESTS();//将会搜索所有已经存在测试案例,然后运行他们
}
  • 死亡测试
ASSERT_DEATH(参数1, 参数2);//程序挂了并且错误信息和参数2匹配,此时认为测试通过。如果参数2为空字符串,则只需要看程序挂没挂即可。
ASSERT_EXIT(参数1, 参数2);//语句停止并且错误信息和被提前给的信息匹配。