美文网首页
开发如何进行单元测试?

开发如何进行单元测试?

作者: DayDayUpppppp | 来源:发表于2021-07-24 22:10 被阅读0次

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

  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 <gtest/gtest.h>

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 <string>
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 <gmock/gmock.h>
#include <string>
#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

相关文章

  • NodeJs单元测试

    本博客简要介绍NodeJs如何进行单元测试 单元测试类型 TDD:测试驱动开发TDD的原理是在开发功能代码之前,先...

  • SpringMVC-单元测试

    良好的开发习惯从单元测试开始。基于Web开发的单元测试可以分为三层进行,Controller层的单元测试、Serv...

  • 开发如何进行单元测试?

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

  • Spring Boot应用的健康监控

    在之前的系列文章中我们学习了如何进行Spring Boot应用的功能开发,以及如何写单元测试、集成测试等,然而,在...

  • 组件测试及发布

    单元测试 在组件开发完成并发布之前,需要对组件进行单元测试[https://www.liaoxuefeng.com...

  • Android 开发如何进行单元测试

    什么是单元测试 单元测试是由一组独立的测试构成,每个测试针对软件中的一个单独的程序单元。单元测试并非检查程序单元直...

  • MockK:Kotlin Mocking 框架

    目录 什么是单元测试? 为什么很多人不愿意做单元测试? 什么是测试驱动开发? 怎么进行测试驱动开发? 为什么要使用...

  • iOS开发——单元测试

    iOS开发——单元测试 iOS开发——单元测试

  • 单元测试

    本文主要侧重于如何在开发过程中使用XCode自带OCUnit框架来做单元测试 一、单元测试概述 什么是单元测试 单...

  • SpringBoot-单元测试

    本文主要介绍如何对SpringBoot进行Service和Controller进行单元测试 Jar依赖 Servi...

网友评论

      本文标题:开发如何进行单元测试?

      本文链接:https://www.haomeiwen.com/subject/awmvmltx.html