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