UT编写规则 https://zhuanlan.zhihu.com/p/424117483
1、遵循AIR原则
A(automation,自动化):单元测试应该是全自动执行的,并且非交互。
I (Independence,独立性):单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
--------反例:method2需要依赖method1的执行,将执行结果作为method2的输入。
R(Repeatable,可重复):单元测试是可以重复执行的,不能受到外界环境的影响。
2、结构
a、 单元测试代码建议有一个新的文件目录
说明:源码构建时会跳过此目录,而单元测试框架默认是扫描此目录。
b、测试用例的包以及结构,需要保持和被测的包以及结构一致。
3、命名
a、测试类命名规范:被测试的业务+Test、被测试的接口+Test、被测试的类+Test
b、测试用例命名规范:测试用例命名应做到简洁全面,可描述。避免使用test1、test2没有含义的名称。其次需要有必要的函数方法注释。
4、禁止使用
a、不要在单元测试中使用Thread.sleep
UT测试类的封装
gtest 中Setup TearDown SetUpTestCase和TearDownTestCase 的区别_惹不起的程咬金的博客-CSDN博客_gtest teardown
使用TEST_F前需要创建一个固件类,继承于::testing::Test类。在类内部使用public或者protected描述其成员,为了保证实际执行的测试子类可以使用其成员变量。在构造函数或者继承于::testing::Test类中的SetUp方法中,可以实现我们需要构造的数据。在析构函数或者继承于::testing::Test类中的TearDown方法中,可以实现一些资源释放的代码。“构造函数/析构函数”和“SetUp/TearDown”的选择,对于什么时候选择哪对,没有统一的标准。一般来说就是构造/析构函数里忌讳做什么就不要在里面做,比如抛出异常等
全局:
class Test:public testing::Environment
{
public:
virtual void SetUp() ;
virtual void TearDown() ;
};
void Test::SetUp()
{
}
void Test::TearDown()
{
}
int main(int argc, char* argv[])
{
testing::AddGlobalTestEnvironment(new Test);
testing::GTEST_FLAG(output) = "xml:test.xml";
testing::InitGoogleTest(&argc, argv);
RUN_ALL_TESTS();
return 0;
}
测试套件:
头文件:
class Test:public::testing::Test
{
public:
static void SetUpTestCase() ;
static void TearDownTestCase() ;
};
**打桩方式 **
打桩的目的主要有:隔离、补齐、控制。隔离是指将测试任务从产品项目中分离出来,使之能够独立编译、链接,并独立运行。隔离的基本方法就是打桩,将测试任务之外的,并且与测试任务相关的代码,用桩来代替,从而实现分离测试任务。例如函数A调用了函数B,函数B又调用了函数C和D,如果函数B用桩来代替,函数A就可以完全割断与函数C和D的关系(隔离了A和C、D,而不是隔离A和B)。
- 补齐是指用桩来代替未实现的代码,例如,函数A调用了函数B,而函数B由其他程序员编写,且未实现,那么,可以用桩来代替函数B,使函数A能够运行并测试。补齐在并行开发中很常用。
- 控制是指在测试时,人为设定相关代码的行为,使之符合测试需求
一、stub打桩
Stub可在gitHub上下载其源码,以源码方式集成,下载地址https://github.com/coolxv/cpp-stub ,提供的头文件名称为stub.h,需要注意的是,如果也引用Mockcpp方式,Mockcpp类也提供了stub头文件,所以需要在stub的stub.h头文件增加命名空间,防止两者冲突。
头文件提供两个函数,set,reset 函数
void set(T addr, S addr_stub)
addr为需要打桩的函数地址,addr_stub为需要替换的函数
void reset((T addr);
将被打桩的函数恢复。
(1)普通函数打桩(非static)
#include<iostream>
#include "stub.h"
using namespace std;
int foo(int a)
{
cout<<"I am foo"<<endl;
return 0;
}
int foo_stub(int a)
{
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set(foo, foo_stub);
foo(1);
return 0;
}
(2)实例成员函数打桩
include<iostream>
#include "stub.h"
using namespace std;
class A{
int i;
public:
int foo(int a){
cout<<"I am A_foo"<<endl;
return 0;
}
};
int foo_stub(void* obj, int a)
{
A* o= (A*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set(ADDR(A,foo), foo_stub);
A a;
a.foo(1);
return 0;
}
例:重载函数打桩 (o_xpath_bool 为需要被打桩的函数)
namespace TinyXPath
{
? ?extern TIXML_API bool o_xpath_bool (const TiXmlNode * XNp_source_tree, const char * cp_xpath_expr);
? ?extern TIXML_API bool o_xpath_bool (const TiXmlNode * XNp_source_tree, const char * cp_xpath_expr, bool & o_res)
}
bool stub_o_xpath_bool (const TiXmlNode * XNp_source_tree, const char * cp_xpath_expr)
{
? ? ?std::cout<<" stub_o_xpath_bool";
? ? return true;
}
TEST(SipXMLParserTest, ParseInfoArrived)
{
.....
bool (*func)(const TiXmlNode * XNp_source_tree, const char * cp_xpath_expr);
func= TinyXPath::o_xpath_bool;
mystub::Stub stub_path_bool;
stub_path_bool.set(func,stub_o_xpath_bool);
......
}
stub打桩在虚函数打桩比较麻烦,需要计算虚函数的地址
虚函数地址的计算:https://blog.csdn.net/qq_42956179/article/details/105428342
二、Mockcpp打桩
Stub因只要能获取到函数地址,即可对函数进行打桩,覆盖返回较广,但每一个打桩的函数需要写一个合适的替换函数,比较麻烦,且某些场景无法满足,如测试函数中多次调用到同一个需要打桩的函,如下例中,如采用stub方式对test打桩,针对test函数不同入参,难以编写一个合适的打桩函数。
int func()
{
int a = test(1);
int b = test(2);
return a + b;
}
Mockcpp 在C函数打桩相对于Stub函数打桩较为方便。
C函数与静态函数打桩
MOCKCPP对C函数及静态函数打桩采用MOCKER方式
(1)mock规范:每个MOCKER(function)开始,跟一系列的.stubs、.with、.will等的内容的整体,称为一个mock规范。
(2)核心关键字:指stubs/defaults/expects/before/with/after/will/then/id等这些直接跟在点后面的关键字。
(3)扩展关键字:指once()/eq()/check()/returnValue()/repeat()等这些作为核心关键字参数的关键字。(AMOCK的follow在mockcpp中是check)
例:MOCKER(UCSP::bIsDomain).stubs().will(returnValue(true));
类函数打桩
采用的是MockObject关键字,无法对普通成员函数打桩。
对虚函数打桩
例:
struct Base0
{
virtual int base00() = 0;
virtual bool base01(int) const = 0;
virtual ~Base0() {}
};
struct Base1
{
virtual void base10() = 0;
virtual long base11(const std::string&) const = 0;
virtual int base12() const = 0;
virtual ~Base1() {}
};
struct Interface: public Base0, public Base1
{
virtual const std::string& a() {}
virtual void b(bool) {}
};
void testShouldBeAbleReturnTheExpectedValue()
{
MockObject<Interface> mock;
mock.method(&Interface::base00).stubs().will(returnValue(20));
TS_ASSERT_EQUALS(20, mock->base00());
TS_ASSERT_EQUALS(20, mock->base00());
TS_ASSERT_EQUALS(20, mock->base00());
}
void testShouldBeAbleToSupportMultipleThenSpecification()
{
MockObject<Interface> mock;
mock.method(&Interface::base00)
.stubs()
.will(returnValue(20))
.then(returnValue(10))
.then(returnValue(1))
.then(returnValue(5));
TS_ASSERT_EQUALS(20, mock->base00());
TS_ASSERT_EQUALS(10, mock->base00());
TS_ASSERT_EQUALS(1, mock->base00());
TS_ASSERT_EQUALS(5, mock->base00());
TS_ASSERT_EQUALS(5, mock->base00());
}
调用GlobalMockObject::verify()释放
GitHub - ez8-co/emock: 🐞 下一代C/C++跨平台mock库 (Next generation cross-platform mock library for C/C++) github上还提及了一种方法 emock,打桩时发现有报错,未深究.
UT覆盖率统计
编译环境为linux,采用的QT平台
**编译命令: **
Pro文件中添加编译属性
QMAKE_CXXFLAGS += -fprofile-arcs -ftest-coverage
QMAKE_LFLAGS+=-fprofile-arcs -ftest-coverage
另添加
QMAKE_CXXFLAGS +=-fno-inline
QMAKE_LFLAGS +=-fno-inline
这两行属性主要是禁止内联,可以获取内联函数地址,从而便于打桩
运行UT工程后会生成.gcna文件
统计覆盖率命令,
使用的是gcov工具
具体命令:
cd ../tmp
lcov -c -d ./ -o test.info --rc lcov_branch_coverage=1 --rc lcov_excl_br_line="new|delete|malloc|free|c_str|.value"
lcov -r test.info "/3rd/" -o MediaSDK.info --rc lcov_branch_coverage=1
/lcov -r test.info -o MediaSDK.info --rc lcov_branch_coverage=1
genhtml --branch-coverage MediaSDK.info --output-directory result
--rc lcov_excl_br_line参数表示去除不想要计算的分支关键字
lcov -r去除不想覆盖的文件目录
版权归原作者 hao_hao先生 所有, 如有侵权,请联系我们删除。