4、难于初始化

  前面介绍自然输入时提到:如果圆的外接正方形面积a1要得到某个预期的值,要传递合适的半径r,这是通过外部输入来获得预期的内部输入,即需要倒推外部输入。这个工作常常是很困难的,如果要设定的不是圆的外接正方形的面积,而是圆的面积,困难得多,例如要求圆的面积为10.00,半径应该是多少?另外,很多时候,为了获得一个简单的内部输入,需要做复杂的初始化工作,请看下面的示例:


/*
功能: 将PERSON对象指针保存到表中,如果名字已存在,则不保存并返回0
参数: pData, 需保存的对象指针
          map, 保存对象指针的映射表
返回: 如果加入失败,返回0,否则返回非0值
*/
int AddPerson(PERSON *pData, CPersonMap *map)
{
    if(map->Search(&pData->name)) return 0;
 
      map->Add(pData);
      return 1;
}


  参数PERSON *pData是结构指针,记录一个“人”的资料,结构PERSON含有一个字符串成员name,记录“人”的名字。参数CpersonMap *map是一个映射表,以名字为key保存PERSON的对象指针。代码很简单,当名字已在表中存在时,直接返回0,否则保存到映射表。测试时要使map->Search(&pData->name)返回true,一般的方法是在表中预先加入相应的数据,这可能很麻烦,如果能直接让map->Search(&pData->name)返回true,既简单又直接。

  难于初始化非常常见,尤其在测试比较高层的函数时,很多输入都是间接输入,本身很复杂,但被测函数并不对其直接读写,只是传递给底层函数以获得一个简单的内部输入,如果我们转换思路,不设定间接输入,而是想办法直接设定内部输入,工作量将会大幅减少。

  5、静态输入

  是指局部静态变量形成的输入。局部静态变量与全局变量一样,每个用例也可能需设定不同的初始值(输入),但却无法外部访问,因此,局部静态变量也会形成内部输入。请看下面的代码:


/*
功能: 游戏程序中用于计算打击效果的代码片断,连续打击时效果随次数递减
参数: reset,为true时重置打击次数
返回: int类型,打击效果
*/
int PowerEffect(bool reset)
{
    //打击次数,由于是局部变量,用例中无法访问,形成测试难点
staticint times = 0;
if(reset) times = 0;
        times++;
 
    int effect[] = {9, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
    if(times >= sizeof(effect) / sizeof(effect[0]))
        return 0;
    return effect[times];
}


  打击次数times是一个局部静态变量,局部变量无法外部访问,这给测试造成了困难。这个示例中,打击次数只是简单递加,还有可能通过适当排列用例,或插入若干前置调用来控制它的值,但在实际项目中,未必那么简单,因此,静态输入也是一种必须解决的内部输入。

  6、中断输入

  中断输入常见于嵌入式项目。是指被测函数运行过程中,在某个位置,系统调用了一个中断函数,该函数修改了某个全局变量,如果被修改的全局变量对被测函数的功能逻辑造成影响,那么测试时也必须考虑。

  7、总结

  前面列出了内部输入的六种情形,后五种是影响单元测试能否顺利实施的关键难点。有趣的是,内部输入的问题通常会被归结为“代码可测性差”,解决办法是改良代码提高可测性,这是不现实的,这些问题大量存在并且多数不可能消除。单元测试方法或工具,如果无法解决内部输入问题,无法适应实际项目的测试,这不是代码可测性问题,而是方法或工具的可用性问题。解决内部输入的方法主要有两种:编写桩代码,底层模拟,下篇文章会进一步介绍。