结构覆盖分析与数据类型

原创, 工作, 适航 2018-07-02 184 0 2787字
- N +

结构覆盖分析与数据类型
王云明
(欢迎转载,但请保留如下链接)
下载PDF版本请点这里
http://yunmingwang.cn/blog/?p=2804

1.前言

DO-178C对A级、B级、C级机载软件的测试提出了结构覆盖的要求。这里结构覆盖包括了几个不同覆盖准则:MC/DC覆盖、判定覆盖、语句覆盖、数据耦合、控制耦合。对于C级软件来说,需要分析语句覆盖、数据耦合和控制耦合;对于B级软件,还需要进一步达到判定覆盖;而对于A级软件来说,还需要进一步达到MC/DC覆盖。具体参见DO-178C附件A的表A-7。

许多人在做这些结构覆盖分析时,常常有许多疑问。DO-178C标准并没有对此给出更加详细的指南,甚至参阅了大量的国内文献后也未能有效解答这些疑问,例如:

  1. 一个布尔表达式的赋值语句,也需要做判定覆盖或MC/DC结构覆盖分析吗?如果要做的话,那么它的意义又是什么呢?
  2. 能不能应用一些变通的技巧可以绕开或者简化结构覆盖分析?而对于这样的技巧,民航审查局方能不能接受?

诸如此类的问题很多,争议更加多。本文试图从深层的原理来解答这类问题,希望能够让民用航空界的朋友们从此对这个问题达成正确的共识。

2.先讲结论

首先,我们开门见山,把结论提出来:一个布尔表达式,不管出现在任何场合,都需要对它做结构覆盖分析(语句覆盖、判定覆盖、MC/DC覆盖,视软件等级而定)。

下面列举一些C语言源代码常见的使用布尔表达式的场合来说明问题。


例1:
if (布尔表达式)
{
    其它语句;
}

例2:
while(布尔表达式)
{
    其它语句;
}

例3:
x = (布尔表达式) ? 数值1:数值2;

例4:
x = 布尔表达式;

如此等等,不一而足。

对于例1、例2、例3,大家都会认为,做结构覆盖分析是合理的。但是对于例4来说,有人会大惑不解,仅仅是一个赋值语句,为什么要做结构覆盖?对它做语句覆盖还有意义,但对它做MC/DC覆盖分析的意义何在?

基本道理是这样的,赋值以后的x,很可能会用于判定,如:


例5:
x = 布尔表达式;
if (x)
{
    其它语句;
}

这时我们可以发现,如果布尔表达式的赋值语句不需要做结构覆盖分析的话,那么我们完全可以通过改写程序来逃避或简化结构覆盖分析的工作了。例如,对于A级软件来说,例1的布尔表达式要做MC/DC覆盖分析;而例5则是对例1的一个改写,刹那间逃避了布尔表达式的MC/DC覆盖分析,只需要做x的判定覆盖就可以了。工作量当然是减少了,但是测试充分性也下降了,软件安全性也下降了。

有些人会对此提出异议,那如果x不用于判定呢?它真的只是赋值语句呢?赋值以后就输出了呢?这样的话,如果强制要求做MC/DC覆盖,那岂不是增加了工作量但是并没有提高测试的充分性?例如:


例6
boolean calculate( ... )
{
    x = 布尔表达式;
    return x;
}

对此,我们的假设是,这个返回值到它的父函数,依然会用于判定。所以,这里的布尔表达式赋值语句依然要做MC/DC覆盖分析。

有些较真的朋友可能还是有异议:如果它的父函数也没有用x来做判定呢?

对于这样的疑问,我们必须要从对布尔表达式做结构覆盖的前提假设和基本原理来说起了。

3.再讲原理

不瞒大家说,在DO-178C的编委中,也曾有人对类似的问题提出过疑虑。但是专家们很快就达成了一致意见,而这个一致意见就是对布尔表达式做结构覆盖的前提假设和基本原理。本人有幸参加DO-178C编委,偶尔学得一招半式,在此把这个问题给大家阐述一下,以帮助大家深刻理解其背后的原理。

首先,我们把源代码里的数据分为两大类型:判定类型和数值类型。

布尔类型是取值为true或者false的数据类型。通常来说,布尔类型存在的意义就是用于逻辑判定、程序分支等情况的。显然布尔类型属于判定类型的一种。

除了布尔类型以外,枚举类型常常用于switch case语句,也用于逻辑判定、程序分支等情况,它也属于判定类型。其实,布尔类型就是一种特殊的枚举类型。

除了用于逻辑判定、程序分支等情况的判定类型以外,其它的所有数据类型,如整数、实数、浮点数、指针、字符、字符串等等,都属于数值类型。它们不会直接用于判定,但有可能会被转化成判定类型再用于判定,例如:


例7:
int a, b;
……
if (a>b)
{
    其它语句;
}

这里的“a>b”,把整数类型先用比较符转化成为布尔类型,然后用于判定。

通过上述分析,我们把数据类型分成了“判定类型”和“数值类型”,判定类型用于逻辑判定或程序分支,而数值类型用于运算,两者各司其职。基于这个分类,我们就很容易理解结构覆盖的前提假设和基本原理:任何判定类型,它的作用就是在程序的某处用来逻辑判定和程序分支的,因此,不论它出现在任何可执行的语句中(你别跟我说出现在注释里!),都需要做结构覆盖。这样,再也不会有人有异议了。

理解上述假设和原理是非常有益的,这个思想也可以用于数据耦合和控制耦合的区分。其实,数据耦合和控制耦合都是函数、模块、分区之间的耦合,当耦合以数值类型的形式出现的时候,它就是数据耦合;当它以判定类型出现的时候,就是控制耦合。这样,刹那间你就分清楚了数据耦合和控制耦合。

如果有人非要挑剔地去挑战和破坏上述的假设和原理,声明一个布尔变量只用于计算而永不用于判定,其实是没有意义的,因为如果真的非要写出这样的程序来,那么这个变量也不算作判定类型。我会友好地对他说,兄弟,好好学习一下编码规范,努力写易读性高的程序,别作了。

但是,如果从反面去挑战和破坏上述的假设和原理,声明一个数值类型的变量却偏偏用于判定,这样是不是就可以逃避结构覆盖分析了呢?例如:


例8:
int x;
x=……;
if (x)
{
    其它语句;
}

这个程序员企图投机倒把,用整数变量 x 来替代布尔型,当 x 是0的时候代表false,当 x 非0的时候代表true,然后把 x 直接用于判定。这种做法在C语言的语法里是允许的,但这显然不是好的编码规范,企图用这种方式来逃避MC/DC覆盖,那更是不被允许的,因为,根据我们的假设,用于判定了,x 就依然是判定类型,即使该程序员强制地、恶意地把它声明成整数类型。因此,更好的程序应该写成这样样子:


例9:
int x;
x=……;
if (x!=0)
{
    其它语句;
}

类似地,对于指针类型,更好的编码习惯应该写成“if (p!=NULL)”而不是“if (p)”。

4.总结

本文把数据类型分成了“判定类型”和“数值类型”,认为判定类型直接用于逻辑判定或者程序分支,而数值类型则用于运算。数值类型不直接用于逻辑判定和程序分支,而需要转化成判定类型以后才可以。

这个假设有着良好的合理性和应用性,并被DO-178C的编委会广泛认可并达成共识。

将这个假设作用于源代码,我们阐述了源代码结构覆盖的基本原理:判定类型的表达式,不论它出现在任何可执行的语句中,都需要做结构覆盖。

将这个假设作用于模块、分区间的耦合,我们可以很简单地区分出数据耦合与控制耦合。

分享到您的社交平台:

发表评论:

请输入您的昵称!(必填)

正确格式为:http://yunmingwang.cn/blog(选填)