技术开发 频道

浅论单元测试的内部输入问题

  难于设定的内部输入

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

  /*

  函数说明:

  功能: 将PERSON对象指针保存到表格中,如果名字已存在,则不保存并返回0

  参数: pData, 需保存的对象指针

  map, 保存对象指针的映射表

  返回: 如果加入失败,返回0,否则返回非0值

  */

  int AddPerson(PERSON* pData, CPersonMap2* map)

  {

  if(map->Search(&pData->name))

  return 0;

  map->Add(pData);

  return 1;

  }

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

  难于设定的内部输入非常常见,尤其是测试比较高层的函数,很多输入都是比较复杂但其目的只是传递给底层函数以获得一个简单的内部输入,如果我们转换思路,想办法直接设定内部输入,工作将会大量减少。

  静态变量形成的内部输入

  除调用底层函数形成内部输入外,局部静态变量也会形成内部输入。请看下面的代码(代码清单4.6.cpp):

  /*

  函数说明:

  功能: 游戏程序中用于计算打击效果的代码片断,连续打击时效果随次数递减

  参数: reset, 输入参数,为true时重置打击次数

  返回: int类型,打击效果

  */

  int PowerEffect(bool reset)

  {

  //打击次数,由于是局部变量,用例中无法访问,难于测试

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

  前面列出了内部输入的五种情形,后四种是影响单元测试能否顺利实施的关键难点。有问题并不可怕,可怕的是不知道问题的存在。只有发现和正视问题,才有可能解决问题。有趣的是,有些朋友将内部输入的问题被归结为“代码可测性差”,解决办法是改良代码提高可测性,这是典型的“站着说话不腰疼”,有经验的程序员用脚趾头想一下,就知道这些问题是大量存在并且多数是不可能消除的。单元测试方法或工具,如果无法解决内部输入的问题,就无法适应实际项目的测试,这不是代码的可测性问题,而是方法或工具的可用性问题。

0
相关文章