2

这是一个超级简单的例子,在这里用 C 语言来说明一个我不知道如何通过测试将其暴露为 bug 的细微错误。

考虑:

#include <stdio.h>

int main() 
{
  int a;
  int b;
  int input;

  printf("Enter 1 or 2: ");
  scanf("%d", &input);

  switch(input) {
  case 1:
    a = 10;
    /* ERROR HERE, I FORGOT A BREAK! */
  case 2:
    b = 20;
    break;
  default:
    printf("You didn't listen!\n");
    return 1;
    break;
  }

  if(input == 1) {
    b = 30;
    printf("%d, %d\n", a, b);
  } else {
    printf("%d\n",b);
  }

  return 0;
}

如代码中所述,break缺少 a ,因此当输入 1 时,它会落入案例 2。但 1 的输出并没有反映这一点,因为它b稍后会覆盖。因此,我们可以设计的所有测试,比如从集合中输入一个数字,{1, 2, 10}都会产生正确的输出。

实际上,内部的分配switch可能非常昂贵,因此这个错误可能非常昂贵。但是,假设从第一天开始就是这样写的,没有基准可以看出成本高于预期。

那么可以做些什么来清除这些类型的错误呢?有没有办法设计测试用例以在生产软件中公开它?

编辑 所以我想我并不完全清楚——我用 C 编写它来说明遇到的问题的类型,但实际上它并不特定于 C。我想要说明的一点是代码进入了我们的部分从来没有打算进入(在这种情况下,因为忘记break说明这一点)。我的实际案例是一个有 700,000 行的 Fortran 代码,它进入了我们从未打算进入的分支,因为 if/switch 设计不佳,从语言的角度来看这是合法的,但可能非常昂贵。

是否有可能设计一个测试或查看来自某些工具的一些数据,这些数据会告诉我们它正在进入不应该进入的分支?我在打印“我不应该在这里!”时发现了一个错误。在所有的情况下,看到它被打印出来,肯定有比随机看到它并放置打印语句更好的方法。

4

5 回答 5

1

对于您的具体示例,根据定义,这绝不是错误/错误。语言中需要失败的可能性。如果你想禁止某些危险的语言特性,那么linter就是要走的路。

为了避免完全无法预料的错误,有一条规则:始终进行防御性编码,并在assert可以放置它们的任何地方使用 s。

于 2012-12-03T06:47:47.453 回答
1

案例 1 的正确状态是不会设置 b。

检查是否设置了 b。

如果您稍后设置 b,您可能需要将代码分解成更小的部分来测试它,但这只是很好的模块化。

似乎您在问“我如何测试不可测试的代码?”。答案是编写可测试代码需要技巧和计划,不能只是事后才想到。

网上有很多东西可以帮助你编写可测试的代码:

https://www.google.com/search?q=writing+testable+code

于 2012-12-03T06:22:01.223 回答
1

您可以为 switch 语句定义编码约定,以便每个分支都会施加一个特殊的状态。就像一个变量被分配了这个案例的值。例如:

switch (v) {
case 1: 
    vcheck = 1;
    ...
    break;
case 2:
    vcheck = 2;
    ...
    break;
}

vcheck在您的测试用例中进行测试。

除此之外,您可以使用对MISRA 规则的验证执行静态代码分析的工具- 并将它们纳入您的构建过程。他们会引起一些想法...... :-)

最后,(我最喜欢的)您可以编写一个脚本来检查此类情况并再次发出警告。

于 2012-12-03T06:11:20.367 回答
1

为什么这是我问的错误?使用任何类型的黑盒测试代码都有效。所以,如果唯一的要求是代码可以工作,那么就没有错误。

但是代码有缺陷。这种缺失break使代码更难理解,更难维护。

编码约定是关于代码外观的规则。遵守编码约定不能通过测试编译的程序来完成,它们必须在源代码上完成。

测试编码约定是通过代码检查(自动或手动)完成的。

编辑:

如果您担心性能,请使用检测工具查找“热点”。你会发现大部分的执行时间可能只花在了几个模块上。查看这些模块以及对它们的每次调用。你会发现只需要查看 10-30 000 行代码。由于审稿范围有限,审稿时间应为1-3周。

底线:在发现细微错误方面,代码审查远远优于测试。

于 2012-12-03T09:55:40.410 回答
0

如果input==1b = 30在输出中看到,你就知道出了点问题。另外,请记住在 else 子句中,您应该在读取之前向 b 写入一些内容。在default:,(例如input==100)的情况下,您最终可能会在没有正确设置的情况下从某个位置读取。

此外,代码审查,如果你能负担得起的话,应该对找到类似的东西有很大帮助。

于 2012-12-03T06:31:14.900 回答