龙空技术网

全程软件测试(一一七):白盒测试方法—读书笔记

阳哥做IT笔记 146

前言:

眼前你们对“java黑盒测试”可能比较关注,小伙伴们都想要了解一些“java黑盒测试”的相关资讯。那么小编也在网摘上网罗了一些关于“java黑盒测试””的相关内容,希望各位老铁们能喜欢,各位老铁们一起来学习一下吧!

一、 测试方法的分类静态测试方法动态测试方法

1 静态测试方法

不执行程序的测试方法。主要用于测试文档和代码(文档)。

2 动态测试方法

通过运行程序来发现缺陷的测试方法。

黑盒测试方法

白盒测试方法

2.1 黑盒测试

也称为功能测试、数据驱动测试、基于规格说明书测试。

从用户观点出发,主要以软件规格说明书为依据,对程序功能和接口进行测试,对输入输出数据之间的对应关系进行测试。

它不涉及到程序的内部结构,如果外部特性本身有问题或规格说明书有问题,则无法察觉。

安全性测试、互操作性测试也属于功能测试。

方法如大纲法、场景法、等价类、边界值、决策表、错误猜测等。

黑盒测试方法还用于测试软件的非功能性特性。

非功能测试用于测试系统工作的怎么样,包括但不限于:

1、可用性/可靠性/稳定性/健壮性/可恢复性测试

2、可维护性测试

3、易用性测试

4、可移植性/兼容性测试

5、配置测试

6、文档测试

7、国际化测试/本地化测试

当不涉及程序内部结构时,上述测试类型也使用黑盒测试方法。

2.2 白盒测试

也称结构测试、逻辑驱动测试、基于程序本身的测试、程序员测试。

结构测试需要完全了解程序结构和处理过程,按照程序内部逻辑测试程序,检验程序中每条通路是否按照预定要求工作。

2.3 黑盒测试与白盒测试的区别

二、 静态测试方法

静态测试方法包括评审和静态分析方法。

1 评审

1.1 评审的含义、过程和目的

1.2 评审的角色

1.3 评审的分类

文档审查代码审查代码走查

1.4 代码审查

1.4.1 代码审查的含义、过程和目的

1.4.2 代码审查的方法和范围

具体做法方法

互查

通常合格的代码应具备正确性、清晰性、规范性、一致性和高效性,概括起来,代码审查的工作涵盖下列方面

1、业务逻辑的审查

2、算法的效率

3、代码风格

if(1==j) 与 if(j==1),问:以上哪种代码风格较好?

if (j>MAX_NUM) 与 if (j>2000),哪个好?

4、编程规则

1.5 代码走查

2 静态分析方法

2.1 数据流分析

使用了未声明/定义的变量变量声明了没有使用

2.2 控制流分析

2.3 复杂度分析

复杂度分析给出一组能描述程序代码的复杂度特征的度量。

计算复杂度

3 静态测试的意义

4 静态测试可以发现的缺陷

引用一个没有定义值的变量;从未使用的变量;模块和组件之间接口不一致;不可达代码(unreachable code)或死代码(dead code);违背编程规则;安全漏洞;代码和软件模型的语法错误等。

5 一些静态分析工具

[OSS]代表开源软件,[PROPRIETARY]代表付费软件。

三、 白盒测试方法

1 单元测试用例的设计方法

白盒测试方法黑盒测试方法以白盒测试方法为主,并适当地结合黑盒测试方法

2 白盒测试方法

逻辑覆盖法

1、语句覆盖

2、判定覆盖

3、条件覆盖

4、判定-条件覆盖

5、条件组合覆盖

路径覆盖法

3 白盒测试方法的步骤

3.1 获得需求、获得/画出程序流程图/算法图

3.2 画出控制流图

根据需求来画

根据算法图/流程图来画

弄清预期结果

3.3 选择覆盖方法设计测试用例

3.3.1 语句覆盖法 C0

目标

程序中的每个可执行语句至少被执行一次。

度量(覆盖率)

被执行的语句数/所有可能的语句数。

被执行的路径数/所有可能的路径数。

用例

a=2,b=1,c=6

用例对语句的覆盖率:100%

用例对路径的覆盖率:25%

语句覆盖能发现语句错误

语句覆盖不能发现逻辑错误/条件错误

3.3.2 分支/判定覆盖 C1

目标

程序中的每个判定的取真分支和取假分支至少执行一次。

用例

a=2,b=1,c=6

a=-1,b=1,c=1

用例对语句的覆盖率:100%

用例对路径的覆盖率:50%

分支/判定覆盖能发现逻辑错误分支/判定覆盖不能发现组合判断中的条件错误

3.3.3 条件覆盖 C2

目标

程序每个判定中每个条件的可能取值至少满足一次。

未必比 C1 更全面。

不能发现逻辑错误。

用例

思考:覆盖率?

3.3.4 判定-条件覆盖 C1+C2

目标

每个条件中的所有可能取值至少执行一次,同时,每个判定的可能结果至少执行一次。

可能会导致某些条件掩盖了另一些条件。

用例

3.3.5 条件组合覆盖/多条件覆盖 C3

目标

每个判定中的所有的条件取值组合至少执行一次。

比 C2 全面。

用例

3.3.6 路径覆盖 C4

目标

用例覆盖程序中的所有可能的执行路径。

不切实际

因为涉及到相当长和几乎无穷尽的路径数。

任何可能的循环在程序段中都被视为是可能的路径。

用例

路径覆盖优化

McCabe 的基路径方法

从源节点到汇节点的线性独立路径数(根据圈复杂度计算)

V(G)=e-n+2p=10-7+2=5

当规模很小时,我们可以直观地标识独立路径。

以下给出的是用节点/边序列表示的路径:

p1:A,B,C,G/1,4,9

p2:A,B,C,B,C, G/1,4,3,4,9

p3:A,B,E,F,G/1,5,8,10

p4:A,D,E,F,G/2,6,8,10

p5:A,D,F,G/2,7,10

案例

四、 白盒测试工具内存资源泄漏检查工具

如 Numega 中的 BounceChecker,Rational 的 Purify 等;

代码覆盖率检查工具

如 Numega 的 TrueCoverage , Rational 的 PureCoverage , TeleLogic 公 司 的

Logiscope;

代码性能检查工具

如 Logiscope 和 Macabe 等。

静态源代码分析工具

类似于编译器,能够检查源代码,发现违反编程语言语法规则和大量定义编程规范的代码。

五、 使用 JUnit 进行单元测试

1 JUint 简介

JUnit 是一个开放源代码的 Java 测试框架,用于编写和运行可重复的测试。

JUnit 测试是程序员测试,即所谓白盒测试,是一个 Java 语言的单元测试框架,多数 Java 的开发环境都已经集成了 JUnit 作为单元测试的工具。

JUnit 在极限编程和重构(refactor)中被极力推荐使用,因为在实现自动单元测试的情况下可以大大的提高开发的效率。

每编写完一个函数之后,都应该对这个函数的方方面面进行测试,这样的测试我们称之为单元测试。

在编写大型程序的时候,需要写成千上万个方法或函数,也许我们在程序中只用到该函数的一小部分功能,并且经过调试可以确定,这一小部分功能是正确的。但是,我们同时应该确保每一个函数都完全正确,因为如果我们今后如果对程序进行扩展,用到了某个函数的其他功能,而这个功能有 bug 的话,那绝对是一件非常郁闷的事情。

2 JUnit 中的注解

JUnit 使用注解进行单元测试。

注解用于修饰测试方法或测试类,写在测试方法或测试类的前面。同时,使用这些注解时,需要导入相应的包。

比较重要的注解大致有 Fixture 注解、@Test 注解、@Ignore 注解、@Parameters 注解、@RunWith 注解。

2.1 Fixture 注解

表示“在某些阶段必然被调用的代码”

一般包括@Before 注解、@After 注解、@BeforeClass 注解、@AfterClass 注解,这些注解一般用于修饰测试方法,测试方法名随意。

@Before 注解

@Before 修饰的方法在每个测试方法之前执行。

@After 注解

@After 修饰的方法在在每个测试方法之后执行。

@BeforeClass 注解

@BeforeClass 修饰的方法在所有测试方法执行之前执行。

@AfterClass 注解

@AfterClass 修饰的方法在所有测试方法执行之后执行。

2.2 @Test 注解

@Test 注解

用于修饰测试方法,表示要对被测试类的某个或某些方法进行测试。

@Test(timeout=xxx)注解

xxx 表示时间,以 ms 为单位;

一般称为限时测试或超时测试,用于设置当前测试方法在一定时间内运行完,否则返回错误。

对于那些逻辑很复杂,循环嵌套比较深的程序,很有可能出现死循环,因此一定要采取一些预防措施,限时测试是一个很好的解决方案,给测试函数或方法设定一个执行时间,超过了这个时间,程序就会被系统强行终止,并且系统还会汇报该函数结束的原因是因为超时,这样就可以发现这些Bug 了。

2.3 @Ignore 注解

@Ignore 注解用于修饰测试方法,表示忽略测试用例;

其含义是“某些方法尚未完成,暂不参与此次测试”。这样的话测试结果就会提示你有几个测试被忽略,而不是失败。一旦你完成了相应函数,只需要把@Ignore 标注删去,就可以进行正常的测试。

2.4 @Parameters 注解

用于修饰产生数据集合的方法,用于参数化。

测试时,可能需要测试大量不同的路径,从而要使用大量的数据,使用@Parameters注解只需要编写一个测试代码即可众多数据进行多次测试。

2.5 @RunWith 注解

用于指定一个测试运行器(Runner),@RunWith 用来修饰类,而不能修饰函数。

只要对一个类指定了 Runner,那么这个类中的所有函数都被这个 Runner 来调用。常用的内置测试运行器有 Parameterized(参数化数据)和 Suite(测试集)。

3 JUnit 中的方法

3.1 断言

用来断定程序中是否存在缺陷。

assertEquals(预期结果,实际结果)

用于测试期望结果的断言,即测试两个对象是否相等,这个方法使用非常多。

fail(错误消息内容)

其中错误消息可选,假如提供,将会在发生错误时报告这个消息。该断言会使测试立即失败,通常用在测试不能达到的分支上(如异常)。

3.2 setUp 和 tearDown

在实际的测试中我们测试某个类的功能是常常需要执行一些共同的操作,完成以后需要销毁所占用的资源(例如网络连接、数据库连接,关闭打开的文件等),JUnit提供了 setUp 方法、tearDown 方法、setUpBeforeClass 方法、tearDownAfterClass方法。

setUp 方法

在每个测试方法之前都会运行。

主要实现测试前的初始化工作。

tearDown 方法

在每个测试方法结束以后都会执行。

主要实现测试完成后的垃圾回收等工作。

setUpBeforeClass 方法

在所有测试方法执行之前执行。

tearDownAfterClass 方法

在所有测试方法执行之后执行。

实际使用时,setUp 方法可用@Before 注解代替,tearDown 方法可用@After 注解代替,setUpBeforeClass 方法可用@BeforeClass 代替,tearDownAfterClass 方法可用@AfterClass 代替。

4 JUnit 的安装和使用流程

4.1 第 1 步,新建 Java 项目

为项目命名,新建一个包并命名,新建一个类命名如 Calculator(也可以暂时不建类,这个类是被测类),如下图。

4.2 第 2 步,将 JUnit 引入项目

右击项目 Java—>选择“构建路径”—>点击“配置构建路径”,如下图。

在弹出的窗口中点击“库”选项卡,点击“添加库”按钮,如下图。

在弹出的窗口中选择“JUnit”,如下图。

之后选择“JUnit”版本,如下图。

最后的效果如下图。

4.3 第 3 步,编写被测类的代码

这是一个能够简单实现加减乘除、平方、开方的计算器类,然后对这些功能进行单元测试。编写完成后,类无需编译。

public class Calculator {				private static int result; // 静态变量,用于存储运行结果				public void add(int n) {				result = result + n;}public void substract(int n) {				result = result - 1; //Bug: 正确的应该是 result =result-n}public void multiply(int n) {}//此方法尚未写好public void divide(int n) {				result = result / n;}public void square(int n) {				result = n * n;}public void squareRoot(int n) {				for (; ;) ;//Bug: 死循环}public void clear() {//将结果清零				result = 0;}public int getResult() {				return result;}}

4.4 第 4 步,创建 JUnit 测试用例类

在 Eclipse 的“包资源管理器”中找到 Calculator 类,右击,在弹出菜单中选择“新建”—>“JUnit 测试用例”,如下图。

在打开的窗口中选择“新建 JUnit 测试”,这里使用注解方式编写测试方法,而没有使用 setUp 等方法,所以这些方法均没有选中,如下图。

需要注意

测试类(如 CalculatorTest)是一个独立的类,没有任何父类。测试类的名字可以任意命名,没有任何局限性,所以我们不能通过类的声明来判断它是不是一个测试类,它与普通类的区别在于它内部的方法的声明。

在下图中选择需要测试的方法,在前面打勾。

之后系统会自动生成一个新类 CalculatorTest,里面包含一些空的测试用例,之后需要对这些测试用例进行修改才可使用。如下图所示。

4.5 第 5 步,运行测试代码

在步骤 4 的基础上编写测试代码(这里暂时不编写,后文给出代码),右击CalculatorTest 类,选择“运行方式”—>“JUnit 测试”,如下图。

4.6 第 6 步,分析测试结果

如下图。结果的具体含义后文将通过具体案例来说明。

红色长条是测试时出现了错误或者故障,如图中的测试结果是:

红条上面的“运行次数:5/5 错误:0 故障次数:4”,表示共进行了 5 个测试,其中 0 个测试代码出现错误,4 个测试失败(这4 个失败的测试,未必是真正发现了缺陷,因为有的测试代码还没有编写)。

5 JUnit 单元测试案例

5.1 在测试类中创建一个被测类对象

private static Calculator calculator=new Calculator();

5.2 使用@Test 进行测试

这里以测试 Calculator 类的 add 方法为例进行说明。

被测试的 add 方法的代码如下:

public void add(int n) {result = result + n;}

获取结果的方法 getResult 的代码如下:

public int getResult() {return result;}

测试 add 方法的类

@Testpublic void testAdd() {fail("尚未实现");}

其中@Test 表示其后的方法 testAdd 是测试方法。

对于测试方法的声明有如下要求

名字可以随便取,没有任何限制,但是返回值必须为 void,而且不能有任何参数。

编写测试代码

@Testpublic void testAdd() {calculator.add(2);assertEquals(2,calculator.getResult());calculator.add(3);assertEquals(5,calculator.getResult());}

说明:

calculator.add(2)表示调用 add 方法,参数为 2,由于 Calculator 类中使用

private static int result 定 义 了 变 量 result , 其 初 值 为 0 , 所 以 执 行 完calculator.add(2)后,预期结果应该是 2。

assertEquals 是 Assert 类中的一个静态方法,一般的使用方式是 Assert.assertEquals(),使用此语句时需要包含包 org.junit.Assert.*,实际包含包的语句是 import static org.junit.Assert.*,其中使用了静态包含(使用 static关键字)后,前面的类名就可以省略了,使用起来更加方便。

这 里 的 测 试 代 码 中 使 用 assertEquals(2,calculator.getResult()) 表 示 执 行calculator.getResult()方法获得结果与 2 进行比较。

运行测试方法,结果如下图。

测试方法列表(prog.CalculatorTset 下面的多行)

绿色对勾:表示测试通过

蓝色差号:表示测试代码有错误或者测试失败

选中某个测试方法后,故障跟踪部分会显示测试代码的错误或者发现的缺陷,如果没有缺陷则什么都不显示。

5.3 使用@Before 进行测试前准备

5.3.1 测试 divide 方法

divide 方法的代码如下:

public void divide(int n) {result = result / n;}

编写测试方法,代码如下:

@Testpublic void testDivide() {calculator.add(8);calculator.divide(2);assertEquals(4, calculator.getResult());}

说明:

calculator.divide(2)表示将 result 的值除以 2 后放入 result 中,最后 result 应该等于 4;

使用 assertEquals(4, calculator.getResult())表示判断结果 result 是否等于 4。运行测试代码,结果如下图。

点击 testDivide 方法,在故障跟踪下可以看到“expected:<4> but was:<6>”

表示预期结果是 4,实际结果是 6,测试没有通过。

我们看代码中的语句 result = result / n,应该是没有问题的,但是为什么这里测试代码却发现了缺陷?事实上,如果我们应该回忆一下在执行完testAdd 方法时 result 已经等 于 5 了!调 用 testDivide 方法时 ,执行calculator.add(8)后 result 等于 13,执行 calculator.divide(2)后 result 等于 6,正好与故障跟踪中的信息一致!

这说明,测试方法 testAdd 和 testDivide 发生了相互干扰,如果期望多个测试方法间相互独立,即执行每个测试方法前都希望 result 等于 0,可以使用@Before 注解。

5.3.2 加入@Before 注解

在 CalculatorTest 类中添加一个方法,用于在执行每个测试方法前先将 result 变量的值清零。代码如下:

@Beforepublic void initiate(){calculator.clear();}

calculator.clear 方法将用于将结果 result 清零,代码如下:

public void clear() {result = 0;}

@Before 在此处不能省略,表示执行每隔测试方法前先执行@Before 修饰的方法。

@Before 后的方法名随意,只要符号命名规则即可。

重新执行测试,结果显示测试通过了。运行结果如下。

5.3.3 测试 substract 方法

substract 方法的代码如下:

public void substract(int n) {result = result - 1; //Bug: 正确的应该是 result=result-n}

编写测试方法,代码如下:

@Testpublic void testSubstract() {calculator.add(10);calculator.substract(2);assertEquals(8, calculator.getResult());}

运行测试后,结果如下图

结果显示 substract 方法存在缺陷,预期结果是 8,实际结果是 9。

5.4 使用@Ignore 忽略测试

multiply 方法并没有编写方法体,可以使用@Ignore 忽略该测试。测试代码如下。

@Ignore("代码未实现,暂时不测")@Testpublic void testMultiply() {}

测试运行结果如下图,由于测试被忽略了,其结果显示的是测试通过。

testMultiply 方法不必编写具体的代码,待将来 multiply 方法实现以后,就可以编写 testMultiply 方法了。

@Ignore("代码未实现,暂时不测")表示后面的测试方法暂不执行

注意:其后的@Test 注解不能省略。

运行次数:5/5(1 skipped)表示 1 个测试被忽略。

5.5 使用@Test(timeout=?)进行超时限制测试

在前面代码的基础上,继续测试 squareRoot 方法,squareRoot 方法的代码如下:

public void squareRoot(int n) {for (; ;) ;//Bug: 死循环}

当方法中使用循环时,使用@Test(timeout=?)可以测试方法中的循环是否陷入了死循环。测试代码如下:

@Test(timeout=2000)public void testSquareRoot() {calculator.squareRoot(4);assertEquals(2, calculator.getResult());}

测试运行结果如下图。

timeout 的单位是毫秒,根据具体情况设置此时间即可。

5.6 使用@RunWith 和@Parameters 进行参数化测试

5.6.1 什么是参数化

怎么测试多分支?

如一个对考试分数进行评价的函数

返回值分别为“优秀,良好,一般,及格,不及格”

在编写测试的时候,如果编写 5 个测试方法,进而测试 5 种情况,是一件很麻烦的事情。

为了简化类似的测试,JUnit 提出了“参数化测试”的概念,只写一个测试函数,把这若干种情况作为参数传递进去,一次性的完成测试。

5.6.2 创建用@RunWith(Parameterized.class)注解的测试类

继续使用先前的例子,测试函数 square,其代码如下:

public void square(int n) {result = n * n;}

这里假设暂且测试三类数据:正数、0、负数。

若使用参数化测试,必须指定一个 Runner,这个 Runner 就是 Parameterized,其写法是@RunWith(Parameterized.class)。

必须生成一个新的测试类,不能与其他测试共用同一个类;

必 须 在 类 前 使 用 @RunWith(Parameterized.class) 指 定 运 行 器 为Parameterized;

在类中创建一个 Calculator 的对象以便调用其被测试的方法。

这里假设新建测试类命名为 SquareTest,代码如下:

@RunWith(Parameterized.class)//注意包含相应的包public class SquareTest {private static Calculator calculator= new Calculator();}

5.6.3 在新测试类中添加存储参数的成员变量

在类中定义两个变量,一个用于存放参数,一个用于存放期待的结果。

private int param;private int result;

5.6.4 使用@Parameters 修饰测试数据集方法

下面编写获得测试数据集的方法,该方法必须使用@Parameters 标注进行修饰;

方法可以任意命名;

集合中数据的顺序无所谓,但编写该类的测试人员必须记住它们的意义。

下面的代码中第 1 个数字表示参数,第 2 个表示预期结果。

@Parameterspublic static Collection caseData(){return Arrays.asList(new Object[][] { {2, 4}, {0, 0}, {-3, 9} });}

5.6.5 编写类的构造函数

功能是对先前定义的两个参数进行初始化。

注意参数的顺序要和上面的数据集合的顺序保持一致。

如果前面的顺序是{参数,期待的结果},那么构造函数的顺序也要是“构造函数(参数, 预期结果)”。

代码如下:

//构造函数,对变量进行初始化public SquareTest(int param, int result){this.param=param;this.result=result;}

5.6.6 编写测试方法

代码如下

@Testpublic void squareTest(){calculator.square(param);assertEquals(result, calculator.getResult());}

运行结果如下

运行结果显示,3 次测试全部通过。

5.6.7 读取文件中的数据进行参数化

打开文件

File file=new File(文件路径)
读取文件
FileReader bytes=new FileReader(file)BufferedReader chars=new BufferedReader(bytes);
读取文件中的行
while((row=chars.readLine())!=null)
拆分行中的列
row.split("\t")
关闭文件
chars.close()

5.7 使用@RunWith 和@Suite.SuiteClasses 运行测试集

5.7.1 什么是测试集

也称打包测试、测试套件。

在一个项目中,只写一个测试类是不可能的,我们会写出很多很多个测试类。可是这些测试类必须一个一个的执行,比较麻烦。

JUnit 提供运行测试集的功能,将所有需要运行的测试类集中起来,一次性的运行完毕,大大的方便测试工作。

5.7.2 @RunWith(Suite.class)和@Suite.SuiteClasses 注解

打包测试也需要使用一个 Runner,需要向@RunWith 标注传递一个参数Suite.class。

同时,还需要另外一个标注@Suite.SuiteClasses,来表明这个类是一个打包测试类,在内部需要提供需要运行的测试类的名字。

新的测试类的类名随便起一个,内容全部为空既可。

新建一个 Junit 测试用例 AllTest,代码如下:

@RunWith(Suite.class)@Suite.SuiteClasses({ CalculatorTest.class,SquareTest.class })public class AllTest {}

CalculatorTest.class、SquareTest.class 是测试类的名字。

运行结果如下

6 使用 JUnit 的注意事项

测试类和测试方法应该有一致的命名方案。

确保测试与时间无关,不要依赖使用过期的数据进行测试,导致在随后的维护过程中很难重现测试。

测试(测试量或代码规模)要尽可能地小,执行速度快。

不要硬性规定数据文件的路径。

利用 JUnit 的自动异常处理书写简洁的测试代码。

事实上在 JUnit 中使用 try-catch 来捕获异常是没有必要的,JUnit 会自动捕获异常,那些没有被捕获的异常就被当成错误处理。

标签: #java黑盒测试 #白盒测试用什么语言做 #白盒测试需要考虑模块内部的执行过程 #白盒测试需要考虑模块内部的执行过程吗对吗 #白盒测试需要考虑模块内部的执行过程嘛