探索式测试是一种软件测试手段,不是一种具体的软件测试技术(如等价类划分、边界值分析、组合测试等),它需要测试人员同时开展测试学习、测试设计、测试执行和测试结果评估等活动,以持续优化测试工作。怎么样在日常的测试流程中实施探索式测试,同时不与公司的现有研发流程产生冲突呢?SMART原则为探索式测试提供了很好的建议。

  ● Specific(具体的):测试需要一个具体的目标。

  ● Measurable(可度量的):有明确的度量可以评估目标是否达成。

  ● Achievable(可实现的):当前的目标应该是可实现的。这潜在地要求将一个大的目标分解为多个小目标,每个小目标也是具体的、可度量的。此外,跟踪小目标的完成情况也提供了整体进度的可度量性。

  ● Relevant(相关的):目标要切合当前语境,符合团队利益,且不忘企业愿景(vision)。

  ● Time-boxed(有时间限制的):为每个目标设定一个合理的后期限。这是帮助测试人员在固定的时间窗口(time window)中排除不相关干扰、专注工作

  下面引入几个代表性Backend Server Bug 的发现过程来解释一下探索式测试的理论,以及如何在我们的日常测试中加入这个测试手段,提高发现bug 的效率。

  【场景1】输入参数探索式测试Bug:非法的用户Id 发送到了备份的数据中心导致了服务器出现宕机

  通过日常Review 服务器端软件Bug列表,QA测试发现输入参数导致服务器出现异常行为的Bug ,引入对同类型问题的思考,输入参数会导致Server 提供的API产生异常,那么其他应用类型的Server会有这种类似问题?Server 内部是怎么样的一个处理逻辑才会导致crush?

  那么先从自己负责的Server下手,通过静态扫描Code 发现“可疑code”

CmResult CMs****Gsb****RequestPdu::解码(CCmMessageBlock &aData)
{
...........
size_t nPos = strKey.find(‘^‘);//可疑code
if (nPos != string::npos)
{
strKey.resize(nPos);
}
m_user.用户ID = strKey;
.......
return CM_OK;
22}

  看来DEV 下一个逻辑用到了Decode代码解析出来的m_user.用户ID,但是这个值却是依赖于特殊字符’^’作为分隔符,看来我们测试的重点是让解析的值出错,那么肯定会造成程序的出错,到底会出什么错呢?拭目以待了!接下来为了测试出这个logical ,需要design Case走进这个分支的代码,通过背景知识了解,这个解码的方法输入参数CCmMessageBlock &aData,是通过Primary数据中心发到另一个数据中心(GSB)。Case可以写出来了,重点是在Primary数据中心构造一个**Id 带有多个’^’InputValue. 测试结果看到了这个Bug 大家应该知道了,通过探索式的测试方法构造输入参数 ,针对性的找到了这个隐藏的Bug,显然这种针对性的构造输入参数,比模糊式的测试发现的bug 效率会有一定提高。

  接下来我们来看一下开发如何Fix这个问题的,

CmResult CMs****Gsb****RequestPdu::解码(CCmMessageBlock &aData)
{
..............
CmResult rv = CMs****Util::GetFromUniqueKey(strUniqueKey, strKey, strSubKey);//Fix code
m_user.用户ID = strKey;
.........
}

  UniqueKey code logic简单在这里说明一下: not use the specail char to generate the Key use the each selfkey and selfKey length 作为一个单元;显然这种编程风格非常的规范,特别是针对Server,这样企图通过输入参数破坏程序逻辑变得很困难了。

  后,通过对这个bug的学习,采用探索式的思想,需要对Server另外4个APP(App1,App2,App3,App4) 进行统一的静态扫描code;发现App2 存在Null Key issues ,这里不再累述了。

  【场景2】线程安全性探索式测试Bug Run Two MultiThreadTool with two ****.so 1000 thread cause crush

  首先熟悉线程安全性的概念:当对一个复杂对象进行某种操作时,从操作开始到操作结束,被操作的对象往往会经历若干非法的中间状态。调用一个函数(假设该函数是正确的)操作某对象常常会使该对象暂时陷入不可用的状态(通常称为不稳定状态),等到操作完全结束,该对象才会重新回到完全可用的状态。如果其他线程企图访问一个处于不可用状态的对象,该对象将不能正确响应从而产生无法预料的结果。