UT 结构分析笔记

本文仅记录当时的思考,并没有完整详细的解决方案

上下文

在写UT的时候我遇见下面这种情况

//success
EXPECT_CALL(mock,func1()).Willonce(Return(yes);
EXPECT_CALL(mock,func2()).Willonce(Return(yes);
EXPECT_CALL(mock,func3()).Willonce(Return(yes);
EXPECT_CALL(mock,func4()).Willonce(Return(yes);
EXPECT_CALL(mock,func5()).Willonce(Return(yes);
EXPECT_EQ(testObj.testfunc(), OK);
//failed 1
EXPECT_CALL(mock,func1()).Willonce(Return(no);
EXPECT_EQ(testObj.testfunc(), FAIL);
//failed 2
EXPECT_CALL(mock,func1()).Willonce(Return(yes);
EXPECT_CALL(mock,func2()).Willonce(Return(no);
EXPECT_EQ(testObj.testfunc(), FAIL);
//failed 3
EXPECT_CALL(mock,func1()).Willonce(Return(yes);
EXPECT_CALL(mock,func2()).Willonce(Return(yes);
EXPECT_CALL(mock,func3()).Willonce(Return(no);
EXPECT_EQ(testObj.testfunc(), FAIL);

对于每一个分支路径,我们都要写一簇EXPECT_CALL来设置期望,而且我们会发现有很多一样的语句.于是我想要复用这些语句.

解决方案

首先, 我将Return里的值换成变量(注意,gtest中,这里是值传递,也就是不能先设置期望再改变变量值),这样就可以用个循环,每一次循环对应一条路径.

retValue[]={yes,yes,yes,yes,yes}
for (i = 0; i< 5; i++)
{
retValue[i] = no;
EXPECT_CALL(mock,func1()).Willonce(Return(retValue[0]);
EXPECT_CALL(mock,func2()).Willonce(Return(retValue[1]);
EXPECT_CALL(mock,func3()).Willonce(Return(retValue[2]);
EXPECT_CALL(mock,func4()).Willonce(Return(retValue[3]);
EXPECT_CALL(mock,func5()).Willonce(Return(retValue[4]);
EXPECT_EQ(testObj.testfunc(), FAIL);
retValue[i] = yes;
}

上面的代码还有一个问题,就是当第i个设置为NO后其后面的分支节点不应该被期望调用,所有还需要修改一下

retValue[]={yes,yes,yes,yes,yes}
for (i = 0; i< 5; i++)
{
retValue[i] = no;
switch i {
case 4
EXPECT_CALL(mock,func5()).Willonce(Return(retValue[4]);//no break here and following
case 3
EXPECT_CALL(mock,func4()).Willonce(Return(retValue[3]);
case 2
EXPECT_CALL(mock,func3()).Willonce(Return(retValue[2]);
case 1
EXPECT_CALL(mock,func2()).Willonce(Return(retValue[1]);
case 0
EXPECT_CALL(mock,func1()).Willonce(Return(retValue[0]);
}
EXPECT_EQ(testObj.testfunc(), FAIL);
retValue[i] = yes;
}

这样就可以了, 看起来很不错! 但是...
真的吗?

进一步分析

为什么可以用这种形式写,什么样的代码结构可以写成这样子?
我开始分析代码的分支结构

  1. 每一个分支看成一个结点,通过设置不同期望可以选择不同的子节点
    2. 每个分支都是不同的,可以分别用EXPECT_CALL设置期望,因为Willonce的特性.
EXPECT_CALL(mock,func1()).Willonce(Return(yes);
EXPECT_CALL(mock,func1()).Willonce(Return(no);
//这样写 后面的语句会覆盖前面的,也就是期望调用一次返回no
//如果是期望 第一次返回yes第二次返回no
EXPECT_CALL(mock,func1()).Willonce(Return(yes).Willonce(Return(no);
//另一种不会被覆盖的写法,使用Insequnence
{
Insequence seq;
EXPECT_CALL(mock,func1()).Willonce(Return(yes);
EXPECT_CALL(mock,func1()).Willonce(Return(no);
}

当我把上面的代码分支树图画出来后是这样的,
image
很明显这是一个特殊情况, 因为这段代码里业务逻辑很简单,没什么数据处理的操作,基本都是调用外部依赖的函数比如打开文件向里面写值读值. 期望的就是每个节点都pass,无论哪一个失败都是直接返回fail.所以上面的测试代码很明显不是一个通用的结构,比如如果后面在节点2后面新增一些分支,那上面的测试代码就需要大改了, 所以维护性变差了. 但是按理说这中代码里不应该有复杂的业务逻辑.

那有没有通用的测试代码结构呢?

既然代码的分支结构是树, 只要遍历所用树的路径就行了.
我没有继续深入了, 因为就算实现了也没让UT变的简单,反而变复杂了,而且对于复杂的代码,UT的编写应该按照use case业务的角度去构造数据进行测试,而不是为了行覆盖.

本文作者:天刚刚破晓

本文链接:https://www.cnblogs.com/tggpx/p/18219656

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   天刚刚破晓  阅读(21)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起