在软件测试中,有一些概念我们需要理解:
* Unit Testing - 单元测试
* Smoke Testing - 冒烟测试
* Stress Testing - 压力测试
* Regression Testing - 回归测试
下面一一进行讲解:
冒烟测试
冒烟测试这个名词最早出现在水管工程中,指的是在工程完成之后,用无毒无害的气体灌入管道,然后查看冒烟情况,看看是否有泄漏的地方:
图片来源 -
http://www.york.ca/NR/rdonlyres/xorewdmahb6qj35zoaqvbtlknaadc4xm2p5zph2gwk5ntz2aledpygswshsuh6rjm3n5mm3btpyaanu7s2mjrio7nc/SmokeTestingSewer.jpg
后来,这个词被引入到软件工程领域,形成了软件测试中最基础的一种测试形式。它指的是:当软件开发完成,发布了某个版本之后,针对这一版本,进行一下基础的可用性测试。比如手工的把它启动起来,各种功能用一用,各个按钮点一点,输入一些样本数据,看看功能是否都好使。
可以说,冒烟测试是一种比较原始的测试方法,但是它是各种其它软件测试方法的发展基础。
单元测试
单元测试将系统划分成模块,定义明确的输入输出,然后去测试输入输出是否合格。
因为单元测试要求每一个测试必须有明确的输入输出,对待测试的功能或者函数进行明确的描述和定义,因此可以形成测试代码,进行复用,并且可以将测试本身做为一个系统工程进行开发。可以说,单元测试是软件测试工程的基石,也是各种测试工具的理论基础。
测试驱动开发(TDD)
在单元测试的基础上,形成了Test Driven Development的软件开发方式,简称TDD,即“测试驱动开发”。
在传统情况下,一般是先开发软件,再进行测试。但是在TDD的理念下面,测试要先行[1]。这就需要软件的设计与接口比较明确,然后测试代码和系统的实现代码全部围绕着设计和接口规范展开。下面是TDD的流程图:
图片来源 -
http://upload.wikimedia.org/wikipedia/en/9/9c/Test-driven_development.PNG
先写测试代码,然后写项目实际代码,使得测试可以通过。这样的开发方式被证明非常有效,可以大大提高软件的开发效率,并且使得项目代码的质量持续可控。
回归测试
回归测试英文叫做Regression Testing。Regression的意思是“退化”,顾名思义,Regression Testing的目的就是要发现那些原来已经测试通过的功能中的新BUG。比如我们的项目有一个注册功能,在老版本的测试中,全部测试通过了,但是在最近新发布的版本当中,这个功能由于最近变更的代码出现了新的问题。回归测试的目的在于发现这类问题。
回归测试的方法有手工和自动两种。比如刚才讲到的冒烟测试,就可以做为回归测试的一种手段:每次新产品发布后,让很多人去试用产品中的功能,然后看看能不能发现一些新引入的BUG。
这样的方式比较低效,一般情况下,在真实的产品当中,我们一般还是会使用工具来进行回归测试,进行自动化的测试,这就需要我们将功能拆分为明确的功能单元和模块,变成单元测试,然后用一些工具去自动执行这些单元测试,从而达到回归测试的目的。
压力测试
压力测试把目光的焦点放在系统的性能上面,目标是测试系统是否能够在性能方面达到设计要求。比如一个Web项目要保证同时满足200人在线使用,这就是压力测试要进行量化测试的目标。
我们可以使用人工的方法去进行压力测试,比如找二百人同时用一个Web项目,看看项目是不是还可以正常使用。但是这样的方法既粗糙也无法量化,因此现代化的测试手段要规范得多。
进行压力测试,首先要对压力指标进行量化,比如:项目A当200人在线时,服务器CPU使用率应为x%或以下,占用内存不超过y MB,每用户系统响应不超过z ms。
此外对操作的行为本身也要进行明确的要求,比如:用户在线的定义为在10秒钟内使用A功能,B功能及C功能。。。
将待测试的内容量化后,便可以使用自动化的工具进行测试。
---
以上是软件测试领域中的一些基本概念,接下来我们要针对Java项目,讲讲各种测试工具的使用。首先我们来看看比较流行的单元测试工具JUnit:
JUnit
JUnit是最为广泛使用的单元测试工具,我们可以创建一个maven[2]项目,默认情况下自动使用JUnit做为测试工具:
mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=javaProj -DartifactId=javaProj
如果一切顺利可以看到项目成功生成:
然后,我们可以看一下这个项目的结构:
可以看到里面有个默认的JUnit单元测试AppTest.java,打开这个代码看看内容:
package javaProj;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/[b][/b]
* Unit test for simple App.
*/
public class AppTest
extends TestCase
{
/[b][/b]
* Create the test case
*
* @param testName name of the test case
*/
public AppTest( String testName )
{
super( testName );
}
/[b][/b]
* @return the suite of tests being tested
*/
public static Test suite()
{
return new TestSuite( AppTest.class );
}
/[b][/b]
* Rigourous Test :-)
*/
public void testApp()
{
assertTrue( true );
}
}
在JUnit当中,有Test Suite和Test Case的概念。其中Test Suite可以用来包含多个Test Case,方便管理,而真正执行测试本身的则是TestCase。
在上面的代码中,只有一个单元测试testApp(),并且测试逻辑非常简单:
assertTrue(true);
assertTrue是JUnit提供给我们的测试方法,用于检查括号内的逻辑是否为真,比如:
assertTrue(1 + 1 == 2);
如果测试失败了,则会抛出异常:
注意,这里我们用的JUnit版本为3,在JUnit3当中,约定单元测试的名称必须以test开头,JUnit3才会去执行它。下面我们写个新的单元测试:
package javaProj;
import junit.framework.TestCase;
public class Test1 extends TestCase {
private int i = 3;
public void testAdd() {
assertEquals(1 + 2, i);
i++;
}
public void testAdd2() {
assertEquals(3, i);
}
}
这个测试包含两个unit,分别是:
* testAdd()
* testAdd2()
两个测试都是测试i的值是否为3,但是在testAdd方法中,将i的值加了1,那么testAdd2是否会成功呢?我们可以运行测试看下结果:
发现两个测试都成功了。也就是说,Test1这个测试类,在执行每一个测试功能的时候,会重新实例化,所有的非static的值会被初始化。这是JUnit的设计,其目的是为了保证每一个测试的无关性。
接下来我们看看JUnit4:
JUnit 4
我们继续使用javaProj这个项目,但是要把pom.xml中的JUnit版本更换为4:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
然后我们撰写一个新的测试:
package javaProj;
import static org.junit.Assert.*;
import org.junit.Test;
public class Test1 {
@Test
public void oneAddOneEqualsTwo() {
assertEquals(2, 1 + 1);
}
}
从上面的代码中可以看出来,JUnit4为我们带来一些新的功能:
* 测试方法不必再以test开头。
* 将单元测试用@Test标记。
* Test1不需要扩展TestCase了。
接下来我们用JUnit4来实践TDD:
测试驱动开发TDD(Test Driven Development)
在TDD的模式中,测试与开发要遵守设计和接口规范进行。因此我们首先定义功能接口:
package javaProj;
public interface Calculator {
public int add(int a, int b);
}
要实现的功能是一个计算器,包含一个add方法,用于将两个int数相加并返回结果。接下来我们撰写测试:
package javaProj;
import static org.junit.Assert.assertEquals;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class CalculatorTest {
private int max;
private int a;
private int b;
private Calculator calc;
@Before
public void prepareSomeNumbers() {
this.max = Integer.MAX_VALUE;
this.a = 1;
this.b = 1;
this.calc = new CalculatorImpl();
}
@After
public void testFinished() {
System.out.println("Done.");
}
@Test
public void testAdd() {
assertEquals(2, calc.add(a, b));
}
@Test
public void testOverflow() {
assertEquals((long) max + max, (long) calc.add(max, max));
}
}
在这个测试中有两个新的元素:@Before与@After -- 有的时候,我们在测试之前需要初始化一些数据,测试完成后要清除环境或是执行一些代码,在这种情况下,可以使用@Before与@After。
此外,我们写了两个单元测试:
* testAdd() -- 测试功能是否正常。
* testOverflow() -- 测试Calculator的add方法,在相加两个Java的Integer最大值时,是否会溢出。
此外,这个测试中用到了项目中还未实现的class:
this.calc = new CalculatorImpl();
因此,为了让这个测试通过,我们要实现Calculator接口的功能:
package javaProj;
public class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
}
有了实现方法,我们便可以执行测试,结果如下:
注意(1)中的代码,我们将int数转成long进行比较,但是add方法在两个最大的int数相加的时候就已经溢出了,因此测试失败。(2)中看到结果是java抛出了异常AssertionError。在(3)中我们看到两个"Done",这也印证了JUnit的设计思想:每个测试在执行时,重新实例化测试类,保证测试环境还原到实始状态[3]。
因此,这个异常应该是我们所期待的,因此我们需要将测试逻辑修正一下,让抛出异常成为正确的测试结果:
@Test(expected = AssertionError.class)
public void testOverflow() {
assertEquals((long) max + max, (long) calc.add(max, max));
}
注意到我们添加了expected方法,表示AssertionError正是期待的测试结果,此时重新执行测试:
可以看到测试结果是全部通过了。
小结
在本文的上篇中,我们了解了测试的基本概念,学习了JUnit3和4的基本使用方法,在下篇中,我将介绍另一个测试工具TestNG,并讲一些实际的项目测试中的技巧及方法。
注释
fn1. 严格的来讲TDD的模式中要求测试先行,即先写测试代码,然后撰写项目实际代码,让这个测试代码可以通过测试。但是在实际操作中,我们可以灵活应用,不必死板。
fn2. 有关Maven,请参考蓝点上面的
掌握Maven -
http://bluedash.net/t/qqxp7 这篇文章。
fn3. 如果有需要,对于static数据,要在@After标记的方法中手工将其还原成初始值。
参考资料
Unit Testing -
http://en.wikipedia.org/wiki/Unit_testing
Smoke Testing -
http://en.wikipedia.org/wiki/Smoke_testing
Stress Testing -
http://en.wikipedia.org/wiki/Stress_testing
Regression Testing -
http://en.wikipedia.org/wiki/Regression_testing
Software performance testing -
http://en.wikipedia.org/wiki/Software_performance_testing
Test-driven_development -
http://en.wikipedia.org/wiki/Test-driven_development
JUnit 4 Tutorial 2 – Expected Exception Test -
http://www.mkyong.com/unittest/junit-4-tutorial-2-expected-exception-test/