开发如何进行单元测试?

知乎上面一个讨论很多的问题,如何进行单元测试?同样,知乎上各路科学家讲了各种思路。大概的思路是:

  1. 使用某个单测的库(比如gtest)
  2. 设计一些科学或伪科学的单元测试case
  3. mock各种接口
  4. 提高单元测试覆盖率,尽可能的覆盖各种分支和边界

单元测试是通过在开发阶段进行白盒测试,把问题在开发阶段就发现,不至于传递到测试阶段,或者正式环境。通过单元测试,把一些琐碎且容易错的事情交给单测框架。让开发人员在修改代码之后能感到安心,踏实。只要跑一把单元测试,就能自动化验证程序逻辑的正确性,而无需在提交代码之前提心吊胆、担心会漏掉什么情况没有处理或者自己新加入的逻辑制造了bug。


1. 使用gtest进行单元测试
// 安装
$ git clone [https://github.com/google/googletest.git](https://github.com/google/googletest.git)
$ cd googletest
$ mkdir build && cd build
$ cmake ..
$ make && sudo make install
  • 一个gtest简单的例子
#include 

int sum(int a, int b) {
    return a+b;
}
int multi(int a, int b) {
    return a*b;
}

// 测试集为 MyTest,测试案例为 Sum
TEST(MytestSet, sum_func) {
    EXPECT_EQ(sum(10,20), 30);
    EXPECT_EQ(sum(0,0), 0);
}
TEST(MytestSet, multi_func) {
    EXPECT_EQ(multi(10,20), 200);
    EXPECT_EQ(multi(0,0), 0);
}

int main(int argc, char *argv[]) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
  • 编译完成之后,使用make test执行单元测试:

gtest的框架还是比较简单的,使用了一个test的宏。交给用户定义单测的名称。定义如下:

TEST(test_suite_name, test_case_name) 
{
    // test body ...
}

在测试函数中,调用被测试的函数 对其返回的结果进行assert。gtest提供了EXPECT_和ASSERT_这两种风格的断言,区别在于ASSERT_在失败之后,会立刻返回,不会执行后面的逻辑。gtest在执行的时候,会提供一个比较直观的输出。可以看到那些case通过了,那些case没有通过。

TEST(MyTest, Add) 
{
    EXPECT_EQ(1 + 1, 2);
    ASSERT_EQ(1 + 1, 2);
}

gtest 提供了8 个ASSERT_断言分别是:
ASSERT_TRUE()、ASSERT_FALSE()、ASSERT_EQ()、
ASSERT_NE()、ASSERT_LT()、ASSERT_LE()、ASSERT_GT()和ASSERT_GE()

EXPECT_的断言同样也有 8 个分别是:
ASSERT_TRUE()、ASSERT_FALSE()、EXPECT_EQ()、
EXPECT_NE()、EXPECT_LT()、EXPECT_LE()、EXPECT_GT()和EXPECT_GE()

2. mock测试
void do_something()
{
     db_req();
     // todo
}

db_req()在某些场景里面是一个很重的操作,或者有上下文的状态依赖(比如依赖本地有db,db中的数据是正确的等)。在这些场景中使用单测的话,就需要伪造一个db_req(),这个就是mock。

gmock是gtest中的一个模块,使用gmock可以实现对各种接口的mock。比如,下面的例子对一个get_from_rpc()的接口进行mock,指定一个调用的结果。

// FooInterface.h
#include 
namespace seamless {
class FooInterface {
public:
        virtual ~FooInterface() {}
public:
        virtual std::string get_from_rpc() = 0;
        std::string get_result()
        {
            std::string str = "reuslt is ";
            return str + get_from_rpc();
        }
};
}  

// FooMock.h
#include 
#include 
#include "FooInterface.h"

namespace seamless {
class MockFoo: public FooInterface 
{
    public:
        MOCK_METHOD0(get_from_rpc, std::string());
};
}  
// FooMain.cc
TEST(mock_test, get_result)
{  
    MockFoo mockFoo;
    string value = "Hello World!";
    EXPECT_CALL(mockFoo, get_from_rpc())
            .WillRepeatedly(Return(value));
    
    string result1 = mockFoo.get_from_rpc();
    string result2 = mockFoo.get_result();

    cout << "get_result() return : " << result1 << endl;
    cout << "get_result() return : " << result2 << endl;
    
    EXPECT_EQ(value, result1);
    EXPECT_EQ("reuslt is " + value, result2);
}

int main(int argc, char** argv) 
{
    ::testing::InitGoogleMock(&argc, argv);
    return RUN_ALL_TESTS();
}

// g++ FooMain.cc -lpthread -lgtest -lgmock

更多的使用方法和api可以参考 gmock手册,gmock是通过纯虚函数 + 继承的方式实现mock的,对于非虚函数和普通函数gmock就不太好使了。

代码地址:
https://github.com/zhaozhengcoder/CoderNoteBook/tree/master/example_code/test/gmock

3. 查看代码覆盖率coverage

编译的时候,添加gcc --coverage参数

# 编译
gcc --coverage xxx_mian.c  module.c

# 执行
./main

# 生成某个文件的代码覆盖结果
[linux@ src]~$gcov main.c 
File 'main.c'
Lines executed:100.00% of 5
Creating 'main.c.gcov'

File '/usr/include/c++/8/iostream'
No executable lines
Removing 'iostream.gcov'

[linux@ src]~$gcov module_a.c 
File 'module_a.c'
Lines executed:80.00% of 5
Creating 'module_a.c.gcov'

File '/usr/include/c++/8/iostream'
No executable lines
Removing 'iostream.gcov'

也可以具体查看文件覆盖的结果:

测试代码打包地址:
https://github.com/zhaozhengcoder/CoderNoteBook/tree/master/example_code/test/coverage


小结

写单测的原则:
1)测试用例能验证函数的正确性;
2)测试用例尽可能涵盖边界条件(例如遍历一个链表,头指针是空,只有一个节点,链表有N个节点,N是问题描述下允许的最大节点数等等);
3)一些异常和错误处理(例如往一个函数里传入空指针,传入空串,这个函数能否打印一些log,返回错误码,实现加法的Add函数如何检测和处理溢出等等)最理想的情况下,应该尽量多写测试用例,以保证代码功能的正确性符合预期,具有良好的容错性。如果代码较复杂,条件分支较多,测试用例最好能覆盖所有的分支路径。
4)单测持续集成,自动化运行


参考:
https://blog.jetbrains.com/rscpp/2015/09/01/unit-testing-google-test/
https://paul.pub/gtest-and-coverage/
https://blog.jetbrains.com/rscpp/2015/09/01/unit-testing-google-test/
https://www.zhihu.com/question/27313846/answer/130954707
http://senlinzhan.github.io/2017/10/08/gtest/
gmock:
https://github.com/google/googletest/blob/v1.8.x/googlemock/docs/CheatSheet.md
https://zhuanlan.zhihu.com/p/393954237

你可能感兴趣的:(开发如何进行单元测试?)