软件测试是软件开发阶段中的一个重要环节,良好的测试方法是保证软件质量的根基。软件测试根据软件开发阶段按过程划分为:1.单元测试 2.集成测试 3.确认测试 4.验收测试 5.系统测试。
什么是单元测试呢?
单元测试是指将测试集成到创建的所有代码中,并且在每次执行构建时运行这些测试的过程。在这个过程中,不仅可以进行语法错的检查而且可以方便的进行语义错误的检查。现在有一些常用的单元测试框架:
1.JUnit 2.TestNG 3.Artima SuiteRunner
其中,JUnit在单元测试中占有的比例较大。JUnit是一个开源的测试框架,通过这个框架可以快速的实现测试用例。一个JUnit测试用例就是一个从TestCase派生的独立的类,在类中通常包括一些测试方法。JUnit中提供了很多断言方法用来测试不同的条件,
assertEquals(a, b)
测试a是否等于b(a和b是原始类型数值(primitive value) 或者必须为实现比较而具有equal方法)
assertFalse(a)
测试a是否为false (假),a是一个Boolean数值 。
assertTrue(a)
测试a是否为true (真),a是一个Boolean 数值
assertNotNull(a)
测试a是否非空,a是一个对象或者null 。
assertNull(a)
测试a是否为null,a是一个对象或者null 。
assertNotSame(a, b)
测试a和b是否没有都引用同一个对象 。
assertSame(a, b)
测试a和b是否都引用同一个对象 。
下面就用一个简单的例子来说明如何使用Netbeans里的JUnit来进行测试。
1.创建一个Java应用程序项目,取名叫JUnitTest
2.创建一个Java类,取名叫"Account",代码如下,里面的get和set方法就是要被测试的方法。
public class Account {
private int ID;
private String name;
public String getID() {
return ID;
}
public void setID(int ID) {
this.ID = ID;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.在项目窗口中,按照如下图方式操作:
4.这时会弹出一个“创建测试”的窗体,如图:
解释其中个别选项:“测数初始化函数”会自动创建setUp方法,用于初始化字段、启用日志记录以及重置环境变量等任务的方法,“测试释放方法”自动创建tearDown方法,用于在测试运行完后进行清理工作,“默认方法主体"自动创建测试方法中的代码,可以根据这些代码模板进行修改。
5.修改AccountTest类中的代码,如下,
package junittest;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
public class AccountTest {
public AccountTest() {
}
@Before
public void setUp() {
System.out.println("测试开始");
}
@After
public void tearDown() {
System.out.println("测试结束");
}
@Test
public void testID() {
System.out.println("getID");
Account instance = new Account();
instance.setID(100);
int result = instance.getID();
assertEquals(100, result); //查看ID值是否为100
}
@Test
public void testName() {
System.out.println("getName");
Account instance = new Account();
instance.setName("Lu Yao");
String result = instance.getName();
assertEquals("Zhang Fan", result); //查看Name值是否为Zhang Fan
}
}
6.在项目节点上单击右键,选择“测试”,可以看到如下输出:
testID测试会通过,因为测试的值和设置的值相同
testName测试不会通过,因为测试的值和设置的值不同
这就是测试的过程。下一讲会举一个复杂一点的例子。
上一篇主要以一个小例子来解释了如何在Netbeans下使用JUnit。可能大家只是粗略的会使用JUnit,只有了一个简单的初级感受。我想在这篇日志里,将JUnit的知识梳理一下,然后再以一个小例子来做以下测试的演示。
一、一些概念性问题:
白盒测试——把测试对象看作一个打开的盒子,程序内部的逻辑结构和其他信息对测试人员是公开的。
黑盒测试——已知产品的功能设计规格,不考虑程序内部结构,进行测试证明每个实现了的功能是否符合要求
灰盒测试——介于白盒和黑盒测试之间,即关注输出对输入的正确性,也关注程序内部表现。
回归测试——软件或环境的修复或更正后的“再测试”,自动测试工具对这类测试尤其有用。
单元测试——是最小粒度的测试,以测试某个功能或代码块。一般由程序员来做,因为它需要知道内部程序设计和编码的细节。
二、单元测试的好处 :
A、提高开发速度——测试是以自动化方式执行的,提升了测试代码的执行效率。
B、提高软件代码质量——它使用小版本发布至集成,便于实现人员排错。同时引入重构概念,让代码更干净和富有弹性。
C、提升系统的可信赖度——它是回归测试的一种。支持修复或更正后的“再测试”,可确保代码的正确性。
三、单元测试的针对对象 :
A、面向过程的软件开发针对过程。
B、面向对象的软件开发针对对象。
C、可以做类测试,功能测试,接口测试(最常用于测试类中的方法)。
四、下面是JUnit一些特性的总结:
1) 提供的API可以让你写出测试结果明确的可重用单元测试用例
2) 提供了三种方式来显示你的测试结果,而且还可以扩展
3) 提供了单元测试用例成批运行的功能
4) 超轻量级而且使用简单,没有商业性的欺骗和无用的向导
5) 整个框架设计良好,易扩展,对不同性质的被测对象,如Class,Jsp,Servlet,Ejb等,Junit有不同的使用技巧。
6) 使用断言方法判断期望值和实际值差异,返回Boolean值。
7) 测试驱动设备使用共同的初始化变量或者实例。
8) 测试包结构便于组织和集成运行。
9) 支持图型交互模式和文本交互模式。
五、JUnit的好处和JUnit单元测试编写原则:
好处: A、可以使测试代码与产品代码分开。
B、针对某一个类的测试代码通过较少的改动便可以应用于另一个类的测试。
C、易于集成到测试人员的构建过程中,JUnit和Ant的结合可以实施增量开发。
D、JUnit是公开源代码的,可以进行二次开发。
E、可以方便地对JUnit进行扩展。
编写原则:
A、是简化测试的编写,这种简化包括测试框架的学习和实际测试单元的编写。
B、是使测试单元保持持久性。
C、是可以利用既有的测试来编写相关的测试。
六、JUnit框架组成 :
A、对测试目标进行测试的方法与过程集合,可称为测试用例(TestCase)。
B、测试用例的集合,可容纳多个测试用例(TestCase),将其称作测试包(TestSuite)。
C、测试结果的描述与记录。(TestResult) 。
D、测试过程中的事件监听者(TestListener)。
E、每一个测试方法所发生的与预期不一致状况的描述,称其测试失败元素(TestFailure) 。
F、JUnit Framework中的出错异常(AssertionFailedError)。
JUnit框架是一个典型的Composite模式:TestSuite可以容纳任何派生自Test的对象;当调用TestSuite对象的run()方法是,会遍历自己容纳的对象,逐个调用它们的run()方法。
七、JUnit中常用的接口和类 :
1)Test接口——运行测试和收集测试结果
Test接口使用了Composite设计模式,是单独测试用例 (TestCase),聚合测试模式(TestSuite)及测试扩展(TestDecorator)的共同接口。 它的public int countTestCases()方法,它来统计这次测试有多少个TestCase,另外一个方法就是public void run( TestResult ),TestResult是实例接受测试结果, run方法执行本次测试。
2)TestCase抽象类——定义测试中固定方法
TestCase是Test接口的抽象实现,(不能被实例化,只能被继承)其构造函数TestCase(string name)根据输入的测试名称name创建一个测试实例。由于每一个TestCase在创建时都要有一个名称,若某测试失败了,便可识别出是哪个测试失败。
TestCase类中包含的setUp()、tearDown()方法。setUp()方法集中初始化测试所需的所有变量和实例,并且在依次调用测试类中的每个测试方法之前再次执行setUp()方法。tearDown()方法则是在每个测试方法之后,释放测试程序方法中引用的变量和实例。
开发人员编写测试用例时,只需继承TestCase,来完成run方法即可,然后JUnit获得测试用例,执行它的run方法,把测试结果记录在TestResult之中。
3)Assert静态类——一系列断言方法的集合
Assert包含了一组静态的测试方法,用于期望值和实际值比对是否正确,即测试失败,Assert类就会抛出一个AssertionFailedError异常,JUnit测试框架将这种错误归入Failes并加以记录,同时标志为未通过测试。如果该类方法中指定一个String类型的传参则该参数将被做为AssertionFailedError异常的标识信息,告诉测试人员改异常的详细信息。
JUnit 提供了6大类31组断言方法,包括基础断言、数字断言、字符断言、布尔断言、对象断言。
其中assertEquals(Object expcted,Object actual)内部逻辑判断使用equals()方法,这表明断言两个实例的内部哈希值是否相等时,最好使用该方法对相应类实例的值进行比较。而assertSame(Object expected,Object actual)内部逻辑判断使用了Java运算符“==”,这表明该断言判断两个实例是否来自于同一个引用(Reference),最好使用该方法对不同类的实例的值进行比对。asserEquals(String message,String expected,String actual)该方法对两个字符串进行逻辑比对,如果不匹配则显示着两个字符串有差异的地方。ComparisonFailure类提供两个字符串的比对,不匹配则给出详细的差异字符。
4)TestSuite测试包类——多个测试的组合
TestSuite类负责组装多个Test Cases。待测得类中可能包括了对被测类的多个测试,而TestSuit负责收集这些测试,使我们可以在一个测试中,完成全部的对被测类的多个测试。
TestSuite类实现了Test接口,且可以包含其它的TestSuites。它可以处理加入Test时的所有抛出的异常。
TestSuite处理测试用例有6个规约(否则会被拒绝执行测试)
A 测试用例必须是公有类(Public)
B 测试用例必须继承与TestCase类
C 测试用例的测试方法必须是公有的( Public )
D 测试用例的测试方法必须被声明为Void
E 测试用例中测试方法的前置名词必须是test
F 测试用例中测试方法误任何传递参数
5)TestResult结果类和其它类与接口
TestResult结果类集合了任意测试累加结果,通过TestResult实例传递个每个测试的Run()方法。TestResult在执行TestCase是如果失败会异常抛出
TestListener接口是个事件监听规约,可供TestRunner类使用。它通知listener的对象相关事件,方法包括测试开始startTest(Test test),测试结束endTest(Test test),错误,增加异常addError(Test test,Throwable t)和增加失败addFailure(Test test,AssertionFailedError t)
TestFailure失败类是个“失败”状况的收集类,解释每次测试执行过程中出现的异常情况。其toString()方法返回“失败”状况的简要描述。
下面是一个测试例子,测试一个实现加减乘除的类。先用Netbeans生成一个项目,取名叫Calc,然后在建立一个类Calc.java。
实现加减乘除的类是:Calc.java,代码如下:
package calc;
public class Calc {
public int add(int a,int b){
return a + b;
}
public int minus(int a,int b){
return a - b;
}
public int multiply(int a, int b ){
return a * b;
}
public int divide(int a , int b )throws Exception
{
if(0 == b){
throw new Exception("除数不能为零");
}
return a / b;
}
}
按照上一节介绍的办法去自动生成测试类:CalcTest.java,然后修改测试类如下所示:
package calc;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
public class CalcTest {
public CalcTest() {
}
private Calc ca;
@BeforeClass
public static void setUpClass() throws Exception {
}
@AfterClass
public static void tearDownClass() throws Exception {
}
@Before
public void setUp() {
ca = new Calc();
}
@After
public void tearDown() {
}
@Test
public void testAdd() {
System.out.println("add");
int a = 2;
int b = 3;
Calc instance = new Calc();
int expResult = 5;
int result = instance.add(a, b);
assertEquals(expResult, result);
// TODO review the generated test code and remove the default call to fail.
// fail("The test case is a prototype.");
}
/**
* Test of minus method, of class Calc.
*/
@Test
public void testMinus() {
System.out.println("minus");
int a = 10;
int b = 7;
Calc instance = new Calc();
int expResult = 3;
int result = instance.minus(a, b);
assertEquals(expResult, result);
// TODO review the generated test code and remove the default call to fail.
// fail("The test case is a prototype.");
}
/**
* Test of multiply method, of class Calc.
*/
@Test
public void testMultiply() {
System.out.println("multiply");
int a = 3;
int b = 3;
Calc instance = new Calc();
int expResult = 9;
int result = instance.multiply(a, b);
assertEquals(expResult, result);
// TODO review the generated test code and remove the default call to fail.
// fail("The test case is a prototype.");
}
/**
* Test of divide method, of class Calc.
*/
@Test
public void testDivide() throws Exception {
System.out.println("divide");
int a = 15;
int b = 3;
Calc instance = new Calc();
int expResult = 5;
int result=0;
try{
result = instance.divide(a, b);
}catch (Exception e) {
e.printStackTrace();
//我们期望result = cal.divide(10,5);正常执行;如果进入到catch中说明失败;
//所以我们加上fail。
Assert.fail();//如果这行没有执行。说明这部分正确。
}
assertEquals(expResult, result);
// TODO review the generated test code and remove the default call to fail.
// fail("The test case is a prototype.");
}
}
然后选中该项目,右键--》测试,查看测试结果。
感谢“向前走的螃蟹”的blog(http://www.blogjava.net/hhhaaawwwkkk/),我引用了他总结的理论部分,并针对Netbeans里的JUnit功能对他的例子进行了修改。同时也发现了,使用Netbeans下的JUnit和只单纯使用JUnit确实有很大不同。至于哪里有不同,大家可以点开他的blog对比看一下。
谢谢大家。