assertequals

Assert.assertEquals是什么含义

Assert.assertEquals的含义是:这是个可选的消息,假如提供,将会在发生错误时报告这个消息。

assert 简介:

编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真。可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言,而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新起用断言。

如何使用junit编写测试类

首先我们需要先下载相应的 JUnit 相关的 JAR 包,下载的过程可以去 JUnit 的官方网站,也可以直接通过 Maven 资源仓库来完成。
使用简单的 @Test 注解实现我们的测试方法的编写和执行
准备工作做好之后,接下来我们就可以开始尝试编写壹个简单的测试代码了。首先,我们编写了壹个 Calculator 类,并提供五个方法分别完成加减乘除以及求平方的运算。代码如下:
package net.oschina.bairrfhoinn.main;
public class Calculator {
public void add(int n){
result += n;
}
public void substract(int n){
result -= n;
}
public void multiply(int n){
result *= n;
}
public void divide(int n){
result /= n;
}
public void square(int n){
result = n * n;
}
public int getReuslt(){
return result;
}
public void clear(){
result = 0;
}
private static int result;
}
在测试类中用到了JUnit4框架,自然要把相应地Package包含进来。最主要地一个Package就是org.junit.*。把它包含进来之后,绝大部分功能就有了。还有一句话也非常地重要“import static org.junit.Assert.*;”,我们在测试的时候使用的壹系列assertEquals()方法就来自这个包。大家注意壹下,这是壹个静态包含(static),是JDK5中新增添的壹个功能。也就是说,assertEquals是Assert类中的壹系列的静态方法,壹般的使用方式是Assert. assertEquals(),但是使用了静态包含后,前面的类名就可以省略了,使用起来更加的方便。
另外要注意的是,我们的测试类是壹个独立的类,没有任何父类。测试类的名字也可以任意命名,没有任何局限性。所以我们不能通过类的声明来判断它是不是一个测试类,它与普通类的区别在于它内部的方法的声明,我们接着会讲到。在测试类中,并不是每壹个方法都是用于测试的,所以我们必须使用“注解”来明确表明哪些是测试方法。“注解”也是JDK5的壹个新特性,用在此处非常恰当。我们可以看到,在某些方法的前有@Before、@Test、@Ignore等字样,这些就是注解,以壹个“@”作为开头。这些注解都是JUnit4自定义的,熟练掌握这些注解的含义,对于编写恰当的测试类非常重要。
接下来我们创建壹个测试类 CalculatorTest.java,代码如下:
package net.oschina.bairrfhoinn.test;
import static org.junit.Assert.*;
import org.junit.Test;
import net.oschina.bairrfhoinn.main.Calculator;
public class CalculatorTest {
private static Calculator calculator = new Calculator();
@Test
public void testAdd(){
calculator.add(7);
calculator.add(8);
assertEquals(15, calculator.getReuslt());
}
}
首先,我们要在方法的前面使用@Test标注,以表明这是壹个测试方法。对于方法的声明也有如下要求:名字可以随便取,没有任何限制,但是返回值必须为void,而且不能有任何参数。如果违反这些规定,会在运行时抛出壹个异常。至于方法内该写些什么,那就要看你需要测试些什么了。比如上述代码中,我们想测试壹下add()方法的功能是否正确,就在测试方法中调用几次add函数,初始值为0,先加7,再加8,我们期待的结果应该是15。如果最终实际结果也是15,则说明add()方法是正确的,反之说明它是错的。assertEquals(15, calculator.getResult());就是用来判断期待结果和实际结果是否相等,其中第壹个参数填写期待结果,第二个参数填写实际结果,也就是通过计算得到的结果。这样写好之后,JUnit 会自动进行测试并把测试结果反馈给用户。
如果想运行它,可以在 eclipse 的资源管理器中选择该类文件,然后点击右键,选择 Run As-》JUnit Test 即可看到运行结果。
使用@Test 的属性 Ignore 指定测试时跳过这个方法
如果在写程序前做了很好的规划,那么哪些方法是什么功能都应该实现并且确定下来。因此,即使该方法尚未完成,他的具体功能也是确定的,这也就意味着你可以为他编写测试用例。但是,如果你已经把该方法的测试用例写完,但该方法尚未完成,那么测试的时候无疑是“失败”。这种失败和真正的失败是有区别的,因此 JUnit 提供了壹种方法来区别他们,那就是在这种测试函数的前面加上 @Ignore 标注,这个标注的含义就是“某些方法尚未完成,暂不参与此次测试”。这样的话测试结果就会提示你有几个测试被忽略,而不是失败。壹旦你完成了相应函数,只需要把@Ignore标注删去,就可以进行正常的测试。
比如说上面的测试类 Calculator.java 中,假设我们的 Calculator 类的 multiply() 方法没有实现,我们可以在测试类 CalculatorTest 中先写如下测试代码:
package net.oschina.bairrfhoinn.test;
import static org.junit.Assert.*;
import org.junit.Ignore;
import org.junit.Test;
import net.oschina.bairrfhoinn.main.Calculator;
public class CalculatorTest {
private static Calculator calculator = new Calculator();
… //此处代码省略
@Ignore(“method square() not implemented, please test this later…“)
@Test
public void testSquare(){
calculator.square(3);
assertEquals(9, calculator.getReuslt());
}
}
我们再运行壹次测试,会看到如下结果,从图中可以很明显的看出,方法testSquare() 上的 @Ignore 注解已经生效了,运行时直接跳过了它,而方法testAdd()仍然正常的运行并通过了测试。
使用注解 @Before 和 @After 来完成前置工作和后置工作
前置工作通常是指我们的测试方法在运行之前需要做的壹些准备工作,如数据库的连接、文件的加载、输入数据的准备等需要在运行测试方法之前做的事情,都属于前置工作;类似的,后置工作则是指测试方法在运行之后的壹些要做的事情,如释放数据库连接、输入输出流的关闭等;比如我们上面的测试,由于只声明了壹个 Calculator 对象,他的初始值是0,但是测试完加法操作后,他的值就不是0了;接下来测试减法操作,就必然要考虑上次加法操作的结果。这绝对是壹个很糟糕的设计!我们非常希望每壹个测试方法都是独立的,相互之间没有任何耦合度。因此,我们就很有必要在执行每壹个测试方法之前,对Calculator对象进行壹个“复原”操作,以消除其他测试造成的影响。因此,“在任何壹个测试方法执行之前必须执行的代码”就是壹个前置工作,我们用注解 @Before 来标注它,如下例子所示:
package net.oschina.bairrfhoinn.test;

import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
public class CalculatorTest {
…//这里省略部分代码
@Before
public void setUp() throws Exception {
calculator.clear();
}
@After
public void tearDown() throws Exception {
System.out.println(“will do sth here…“);
}
…//这里省略部分代码
}
另外要说的是,注解 @Before 是定义在 org.junit.Before 这个类中的,因此使用时需要将其引入我们的代码中。这样做了之后,每次我们运行测试方法时,JUnit 都会先运行 setUp() 方法将 result 的值清零。不过要注意的是,这里不再需要 @Test 注解,因为这并不是壹个 test,只是壹个前置工作。同理,如果“在任何测试执行之后需要进行的收尾工作,我们应该使用 @After 来标注,方法与它类似。由于本例比较简单,不需要用到此功能,所以我们只是简单了给它添加了壹个 tearDown() 方法并在收尾时打印壹句话到控制台,并且使用 @After 来注解这个方法。
使用@BeforeClass 和 @AfterClass 来完成只需要执行壹次的前置工作和后置工作
上面我们提到了两个注解 @Before 和 @After ,我们来看看他们是否适合完成如下功能:有壹个类负责对大文件(超过500 MB)进行读写,他的每壹个方法都是对文件进行操作。换句话说,在调用每壹个方法之前,我们都要打开壹个大文件并读入文件内容,这绝对是壹个非常耗费时的操作。如果我们使用 @Before 和 @After ,那么每次测试都要读取壹次文件,效率及其低下。所以我们希望的是,在所有测试壹开始读壹次文件,所有测试结束之后释放文件,而不是每次测试都读文件。JUnit的作者显然也考虑到了这个问题,它给出了@BeforeClass 和 @AfterClass 两个注解来帮我们实现这个功能。从名字上就可以看出,用这两个注解标注的函数,只在测试用例初始化时执行 @BeforeClass 方法,当所有测试执行完毕之后,执行 @AfterClass 进行收尾工作。在这里要注意壹下,每个测试类只能有壹个方法被标注为 @BeforeClass 或 @AfterClass,而且该方法必须是 public static 类型的。
使用@Test 的属性 timeout 来完成限时测试,以检测代码中的死循环
现在假设我们的 Calculator 类中的 square() 方法是个死循环,那应该怎么办呢,比如说像下面这样:
public void square(int n){
for(;;){}
}
如果测试的时候遇到死循环,你的脸上绝对不会露出笑容的。因此,对于那些逻辑很复杂,循环嵌套比较深的、有可能出现死循环的程序,因此壹定要采取壹些预防措施。限时测试是壹个很好的解决方案。我们给这些测试函数设定壹个预期的执行时间,超过了这壹时间,他们就会被系统强行终止,并且系统还会向你汇报该函数结束的原因是因为超时,这样你就可以发现这些 Bug 了。要实现这壹功能,只需要给 @Test 标注加壹个参数timeout即可,代码如下:
@Test(timeout=2000L)
public void testSquare() {
calculator.square(3);
assertEquals(9, calculator.getReuslt());
}
timeout参数表明了你预计该方法运行的时长,单位为毫秒,因此2000就代表2秒。现在我们让这个测试方法运行壹下,看看失败时是什么效果。
使用@Test 的属性expected来监控测试方法中可能会抛出的某些异常
JAVA中的异常处理也是壹个重点,因此你经常会编写壹些需要抛出异常的函数。如果你觉得壹个函数应该抛出异常,但是它没抛出,这算不算 Bug 呢?这当然是Bug,JUnit 也考虑到了这壹点,并且可以帮助我们找到这种 Bug。例如,我们写的计算器类有除法功能,如果除数是壹个0,那么必然要抛出“除0异常”。因此,我们很有必要对这些进行测试。代码如下:
@Test(expected=java.lang.ArithmeticException.class)
public void testDivide(){
calculator.divide(0);
}
如上述代码所示,我们需要使用@Test注解中的expected属性,将我们要检验的异常(这里是 java.lang.ArithmeticException)传递给他,这样 JUnit 框架就能自动帮我们检测是否抛出了我们指定的异常。
指定 JUnit 运行测试用例时的 Runner
大家有没有想过这个问题,当你把测试代码提交给JUnit框架后,框架是如何来运行你的代码的呢?答案就是Runner。在JUnit中有很多个Runner,他们负责调用你的测试代码,每壹个Runner都有其各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。可能你会觉得奇怪,前面我们写了那么多测试,并没有明确指定壹个Runner啊?这是因为JUnit中有壹个默认的Runner,如果你没有指定,那么系统会自动使用默认Runner来运行你的代码。换句话说,下面两段代码含义是完全壹样的:
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class CalculatorTest {
…//省略此处代码
}
//用了系统默认的JUnit4.class,运行效果完全壹样
public class CalculatorTest {
…//省略此处代码
}

java测试引用不明确

jvm不知道你要引用的是哪个assertEquals。
打个比方:云南省有个人叫王二;上海市有个人也叫王二。他们在一起的时候你请B去帮你叫王二过来,B就不知道你想叫的是谁。所以,你就要告诉B是叫云南的还是上海的
这道题的解决方法就是:将参数类型写死。是double型还是Object的。

java对象为空的判断

/** 
     * 判断对象或对象数组中每一个对象是否为空: 对象为null,字符序列长度为0,集合类、Map为empty 
     *  
     * @param obj 
     * @return 
     */  
    public static boolean isNullOrEmpty(Object obj) {  
        if (obj == null)  
            return true;  
  
        if (obj instanceof CharSequence)  
            return ((CharSequence) obj).length() == 0;  
  
        if (obj instanceof Collection)  
            return ((Collection) obj).isEmpty();  
  
        if (obj instanceof Map)  
            return ((Map) obj).isEmpty();  
  
        if (obj instanceof Object) {  
            Object object = (Object) obj;  
            if (object.length == 0) {  
                return true;  
            }  
            boolean empty = true;  
            for (int i = 0; i 《 object.length; i++) {  
                if (!isNullOrEmpty(object[i])) {  
                    empty = false;  
                    break;  
                }  
            }  
            return empty;  
        }  
        return false;  
    }  
应用场景:
读取excel文件,转化为一个二维数组:Object arrays
但是excel中有空行,所以需要过滤Object arrays中的空的一维数组:
Java代码  
/*** 
     * 过滤数组中的空元素 
     *  
     *  
     * @param arrays 
     * @return 
     */  
    public static Object filterEmpty(Object arrays) {  
        int sumNotNull = 0;  
        /*** 
         * 统计非空元素的总个数 
         */  
        for (int i = 0; i 《 arrays.length; i++) {  
            Object object = arrays[i];  
            if (!ValueWidget.isNullOrEmpty(object)  
                    && !SystemUtil.isNullOrEmpty((Object) object)) {// 判断元素是否为空  
                sumNotNull = sumNotNull + 1;  
            }  
        }  
        Object filtedObjs = new Object[sumNotNull];  
        int index = 0;  
        for (int i = 0; i 《 arrays.length; i++) {  
            Object object_tmp = arrays[i];  
            if (!ValueWidget.isNullOrEmpty(object_tmp)  
                    && !SystemUtil.isNullOrEmpty((Object) object_tmp)) {// 判断元素是否为空  
                filtedObjs[index++] = object_tmp;  
            }  
        }  
        return filtedObjs;  
    }  
判断对象的所有成员变量是否为空
Java代码  
/*** 
     * Determine whether the object’s fields are empty 
     *  
     * @param obj 
     * @param isExcludeZero  :true:数值类型的值为0,则当做为空;—-false:数值类型的值为0,则不为空 
     *  
     * @return 
     * @throws SecurityException 
     * @throws IllegalArgumentException 
     * @throws NoSuchFieldException 
     * @throws IllegalAccessException 
     */  
    public static boolean isNullObject(Object obj, boolean isExcludeZero)  
            throws SecurityException, IllegalArgumentException,  
            NoSuchFieldException, IllegalAccessException {  
        if(ValueWidget.isNullOrEmpty(obj)){//对象本身就为null  
            return true;  
        }  
        List《Field》 fieldList = ReflectHWUtils.getAllFieldList(obj.getClass());  
        boolean isNull = true;  
        for (int i = 0; i 《 fieldList.size(); i++) {  
            Field f = fieldList.get(i);  
            Object propertyValue = null;  
            try {  
                propertyValue = getObjectValue(obj, f);  
            } catch (NoSuchFieldException e) {  
                e.printStackTrace();  
            }  
  
            if (!ValueWidget.isNullOrEmpty(propertyValue)) {// 字段不为空  
                if (propertyValue instanceof Integer) {// 是数字  
                    if (!((Integer) propertyValue == 0 && isExcludeZero)) {  
                        isNull = false;  
                        break;  
                    }  
                } else if (propertyValue instanceof Double) {// 是数字  
                    if (!((Double) propertyValue == 0 && isExcludeZero)) {  
                        isNull = false;  
                        break;  
                    }  
                }else if (propertyValue instanceof Float) {// 是数字  
                    if (!((Float) propertyValue == 0 && isExcludeZero)) {  
                        isNull = false;  
                        break;  
                    }  
                }else if (propertyValue instanceof Short) {// 是数字  
                    if (!((Short) propertyValue == 0 && isExcludeZero)) {  
                        isNull = false;  
                        break;  
                    }  
                }else {  
                    isNull = false;  
                    break;  
                }  
            }  
        }  
        return isNull;  
    }  
 测试:
Java代码  
@Test  
    public void test_isNullObject() throws SecurityException,  
            IllegalArgumentException, NoSuchFieldException,  
            IllegalAccessException {  
        Person2 p = new Person2();  
        Assert.assertEquals(true, ReflectHWUtils.isNullObject(p, true));  
        Assert.assertEquals(false, ReflectHWUtils.isNullObject(p, false));  
  
        p.setAddress(“beijing“);  
        Assert.assertEquals(false, ReflectHWUtils.isNullObject(p, true));  
        Assert.assertEquals(false, ReflectHWUtils.isNullObject(p, false));  
  
        p.setAddress(null);  
        p.setId(0);  
        Assert.assertEquals(true, ReflectHWUtils.isNullObject(p, true));  
        Assert.assertEquals(false, ReflectHWUtils.isNullObject(p, false));  
  
    }  
 Person2 源代码(省略getter,setter方法):
Java代码  
import java.sql.Timestamp;  
  
public class Person2 {  
    private int id;  
    private int age;  
    private double weight;  
    private String personName;  
    private Timestamp birthdate;  
    public String identitify;  
    protected String address;  
    String phone;  
      
}

mockito和junit的区别

Mockito是一个开源mock框架;Junit是一个Java语言的单元测试框架。
junit的测试用例:
public class MainTstObjectJMockTest {
private Mockery context = new JUnit4Mockery();
private MayMockObject mayMockObject;
private MainTstObject mainTstObject;
@Before
public void setUp() {
this.mayMockObject = this.context.mock(MayMockObject.class); // Mock了外接接口
this.mainTstObject = new MainTstObject();
this.mainTstObject.setMayMockObject(this.mayMockObject);
}
@Test
public void process() {
final String str1 = “Mockito返回字符串。“;
final String str2 = “Mockito返回字符串。“;
this.context.checking(new Expectations() {
{
oneOf(mayMockObject).getString(str2);
will(returnValue(““)); // Mock返回值
// allowing(mayMockObject).getString(with(any(String.class)));
// will(returnValue(““));
}
});
String ret = this.mainTstObject.create(str1, str2);
Assert.assertEquals(ret, str1);
}
@After
public void tearDown() {
}
}
Mockito的测试用例:
public class MainTstObjectMockitoTest {
// 指定Mock的对象
@Mock
private MayMockObject mayMockObject;
private MainTstObject mainTstObject;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this); // 声明测试用例类
this.mainTstObject = new MainTstObject();
this.mainTstObject.setMayMockObject(this.mayMockObject);
}
@Test
public void process() {
String str1 = “Mockito返回字符串。“;
String str2 = “Mockito返回字符串。“;
Mockito.when(this.mayMockObject.getString(str2)).thenReturn(““); // Mock返回值
String ret = this.mainTstObject.create(str1, str2);
Assert.assertEquals(ret, str1);
}
@After
public void tearDown(){
}
}
junit和Mockito在用法上的一些区别:
1、对于要Mock的对象,JMock要显示指出来【this.mayMockObject = this.context.mock(MayMockObject.class);】;
而Mockito只是简单加上注释【@Mock】,然后声明下测试用例类即可【MockitoAnnotations.initMocks(this);】。
2、对于方法的模拟,junit显得更加复杂了【this.context.checking(new Expectations() {
{
oneOf(mayMockObject).getString(str2);
will(returnValue(““));
}
});】
而Mockito只要简单的一行代码就行【Mockito.when(this.mayMockObject.getString(str2)).thenReturn(““);】。
从上可以看出,Mockito的代码行更少,更加简洁易记!
另外需要注意的是:junit只能Mock接口,也就是上面的例子中【MayMockObject】只能为接口,不能为实体类;而Mockito就没有这个限制。

用python单元测试怎么测一段代码

单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
比如对函数abs(),我们可以编写出以下几个测试用例:
输入正数,比如1、1.2、0.99,期待返回值与输入相同;
输入负数,比如-1、-1.2、-0.99,期待返回值与输入相反;
输入0,期待返回0;
输入非数值类型,比如None、、{},期待抛出TypeError。
把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。
如果单元测试通过,说明我们测试的这个函数能够正常工作。如果单元测试不通过,要么函数有bug,要么测试条件输入不正确,总之,需要修复使单元测试能够通过。
单元测试通过后有什么意义呢?如果我们对abs()函数代码做了修改,只需要再跑一遍单元测试,如果通过,说明我们的修改不会对abs()函数原有的行为造成影响,如果测试不通过,说明我们的修改与原有行为不一致,要么修改代码,要么修改测试。
这种以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合我们设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍然是正确的。
我们来编写一个Dict类,这个类的行为和dict一致,但是可以通过属性来访问,用起来就像下面这样:
》》》 d = Dict(a=1, b=2)
》》》 d[’a’]
1
》》》 d.a
1
mydict.py代码如下:
class Dict(dict):
def __init__(self, **kw):
super(Dict, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r“’Dict’ object has no attribute ’%s’“ % key)
def __setattr__(self, key, value):
self[key] = value
为了编写单元测试,我们需要引入Python自带的unittest模块,编写mydict_test.py如下:
import unittest
from mydict import Dict
class TestDict(unittest.TestCase):
def test_init(self):
d = Dict(a=1, b=’test’)
self.assertEquals(d.a, 1)
self.assertEquals(d.b, ’test’)
self.assertTrue(isinstance(d, dict))
def test_key(self):
d = Dict()
d[’key’] = ’value’
self.assertEquals(d.key, ’value’)
def test_attr(self):
d = Dict()
d.key = ’value’
self.assertTrue(’key’ in d)
self.assertEquals(d[’key’], ’value’)
def test_keyerror(self):
d = Dict()
with self.assertRaises(KeyError):
value = d[’empty’]
def test_attrerror(self):
d = Dict()
with self.assertRaises(AttributeError):
value = d.empty
编写单元测试时,我们需要编写一个测试类,从unittest.TestCase继承。
以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。
对每一类测试都需要编写一个test_xxx()方法。由于unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。最常用的断言就是assertEquals():
self.assertEquals(abs(-1), 1) # 断言函数返回的结果与1相等
另一种重要的断言就是期待抛出指定类型的Error,比如通过d[’empty’]访问不存在的key时,断言会抛出KeyError:
with self.assertRaises(KeyError):
value = d[’empty’]
而通过d.empty访问不存在的key时,我们期待抛出AttributeError:
with self.assertRaises(AttributeError):
value = d.empty
运行单元测试
一旦编写好单元测试,我们就可以运行单元测试。最简单的运行方式是在mydict_test.py的最后加上两行代码:
if __name__ == ’__main__’:
unittest.main()
这样就可以把mydict_test.py当做正常的python脚本运行:
$ python mydict_test.py
另一种更常见的方法是在命令行通过参数-m unittest直接运行单元测试:
$ python -m unittest mydict_test
…..
———————————————————————-
Ran 5 tests in 0.000s
OK
这是推荐的做法,因为这样可以一次批量运行很多单元测试,并且,有很多工具可以自动来运行这些单元测试。
setUp与tearDown
可以在单元测试中编写两个特殊的setUp()和tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。
setUp()和tearDown()方法有什么用呢?设想你的测试需要启动一个数据库,这时,就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码:
class TestDict(unittest.TestCase):
def setUp(self):
print ’setUp…’
def tearDown(self):
print ’tearDown…’
可以再次运行测试看看每个测试方法调用前后是否会打印出setUp…和tearDown…。

junit3和junit4的区别

Junit4支持注解了,只要在要执行的方法前加@Test即可,如:
@Test
public void multiplyPoundsByInteger() {
assertEquals( 10, 5 );
}
Junit4增加了许多特性,主要是支持注解了:
测试由原来的命名模式改变注解,即testXXX变为@Test。其中@Test还提供了额外的属性。如expected,表示期望抛出的异常
数组比较改用Assert.assertArrayEquals
套件测试也用注解替换
通过@Ignore,可以忽略某个方法或整个类的测试
增加了新特性-理论机制(Theory),这个特性听起来很迷惑人,作用是使得开发人员从开始的定义测试用例的阶段就可以通过参数集(理论上是无限个参数)对代码行为进行概括性的总结.开发人员都知道他们代码所想要实现的概括性的总的目的,理论使得他们只需要在一个地方就可以快速的指定这些目的,而不要将这些目的翻译成大量的独立的测试用例。

介绍一下JUnit4

测试的概念
长期以来,我所接触的软件开发人员很少有人能在开发的过程中进行测试工作。大部分的项目都是在最终验收的时候编写测试文档。有些项目甚至没有测试文档。现在情况有了改变。我们一直提倡UML、RUP、软件工程、CMM,目的只有一个,提高软件编写的质量。举一个极端的例子:如果你是一个超级程序设计师,一个传奇般的人物。(你可以一边喝咖啡,一边听着音乐,同时编写这操作系统中关于进程调度的模块,而且两天时间内就完成了!)我真得承认,有这样的人。(那个编写UNIX中的vi编辑器的家伙就是这种人。)然而非常遗憾的是这些神仙们并没有留下如何修成正果的README。所以我们这些凡人--在同一时间只能将注意力集中到若干点(据科学统计,我并不太相信,一般的人只能同时考虑最多7个左右的问题,高手可以达到12个左右),而不能既纵览全局又了解细节--只能期望于其他的方式来保证我们所编写的软件质量。
为了说明我们这些凡人是如何的笨。有一个聪明人提出了软件熵(software entropy)的概念:一个程序从设计很好的状态开始,随着新的功能不断地加入,程序逐渐地失去了原有的结构,最终变成了一团乱麻。你可能会争辩,在这个例子中,设计很好的状态实际上并不好,如果好的话,就不会发生你所说的情况。是的,看来你变聪明了,可惜你还应该注意到两个问题:1)我们不能指望在恐龙纪元(大概是十年前)设计的结构到了现在也能适用吧。2)拥有签字权的客户代表可不理会加入一个新功能是否会对软件的结构有什么影响,即便有影响也是程序设计人员需要考虑的问题。如果你拒绝加入这个你认为致命的新功能,那么你很可能就失去了你的住房贷款和面包(对中国工程师来说也许是米饭或面条,要看你是南方人还是北方人)。
另外,需要说明的是我看过的一些讲解测试的书都没有我写的这么有人情味(不好意思…)。我希望看到这片文章的兄弟姐妹能很容易地接受测试的概念,并付诸实施。所以有些地方写的有些夸张,欢迎对测试有深入理解的兄弟姐妹能体察民情,并不吝赐教。
好了,我们现在言归正传。要测试,就要明白测试的目的。我认为测试的目的很简单也极具吸引力:写出高质量的软件并解决软件熵这一问题。想象一下,如果你写的软件和Richard Stallman(GNU、FSF的头儿)写的一样有水准的话,是不是很有成就感?如果你一致保持这种高水准,我保证你的薪水也会有所变动。
测试也分类,白箱测试、黑箱测试、单元测试、集成测试、功能测试…。我们先不管有多少分类,如何分类。先看那些对我们有用的分类,关于其他的测试,有兴趣的人可参阅其他资料。白箱测试是指在知道被测试的软件如何(How)完成功能和完成什么样(What)的功能的条件下所作的测试。一般是由开发人员完成。因为开发人员最了解自己编写的软件。本文也是以白箱测试为主。黑箱测试则是指在知道被测试的软件完成什么样(What)的功能的条件下所作的测试。一般是由测试人员完成。黑箱测试不是我们的重点。本文主要集中在单元测试上,单元测试是一种白箱测试。目的是验证一个或若干个类是否按所设计的那样正常工作。集成测试则是验证所有的类是否能互相配合,协同完成特定的任务,目前我们暂不关心它。下面我所提到的测试,除非特别说明,一般都是指单元测试。
需要强调的是:测试是一个持续的过程。也就是说测试贯穿与开发的整个过程中,单元测试尤其适合于迭代增量式(iterative and incremental)的开发过程。Martin Fowler(有点儿像引用孔夫子的话)甚至认为:“在你不知道如何测试代码之前,就不应该编写程序。而一旦你完成了程序,测试代码也应该完成。除非测试成功,你不能认为你编写出了可以工作的程序。”我并不指望所有的开发人员都能有如此高的觉悟,这种层次也不是一蹴而就的。但我们一旦了解测试的目的和好处,自然会坚持在开发过程中引入测试。
因为我们是测试新手,我们也不理会那些复杂的测试原理,先说一说最简单的:测试就是比较预期的结果是否与实际执行的结果一致。如果一致则通过,否则失败。看下面的例子:
//将要被测试的类
public class Car {
public int getWheels() {
return 4;
}
}
//执行测试的类
public class testCar {
public static void main(String args) {
testCar myTest = new testCar();
myTest.testGetWheels();
}
public testGetWheels() {
int expectedWheels = 4;
Car myCar = Car();
if (expectedWheels==myCar.getWheels())
System.out.println(“test [Car]: getWheels works perfected!“);
else
System.out.println(“test [Car]: getWheels DOESN’T work!“);
}
}
如果你立即动手写了上面的代码,你会发现两个问题,第一,如果你要执行测试的类testCar,你必须必须手工敲入如下命令:
[Windows] d:》java testCar
[Unix] % java testCar
即便测试如例示的那样简单,你也有可能不愿在每次测试的时候都敲入上面的命令,而希望在某个集成环境中(IDE)点击一下鼠标就能执行测试。后面的章节会介绍到这些问题。第二,如果没有一定的规范,测试类的编写将会成为另一个需要定义的标准。没有人希望查看别人是如何设计测试类的。如果每个人都有不同的设计测试类的方法,光维护被测试的类就够烦了,谁还顾得上维护测试类?另外有一点我不想提,但是这个问题太明显了,测试类的代码多于被测试的类!这是否意味这双倍的工作?不!1)不论被测试类-Car 的 getWheels 方法如何复杂,测试类-testCar 的testGetWheels 方法只会保持一样的代码量。2)提高软件的质量并解决软件熵这一问题并不是没有代价的。testCar就是代价。
我们目前所能做的就是尽量降低所付出的代价:我们编写的测试代码要能被维护人员容易的读取,我们编写测试代码要有一定的规范。最好IDE工具可以支持这些规范。 好了,你所需要的就是JUnit。一个Open Source的项目。用其主页上的话来说就是:“JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing work)。用于Java开发人员编写单元测试之用。”所谓框架就是 Erich Gamma 和 Kent Beck 定下了一些条条框框,你编写的测试代码必须遵循这个条条框框:继承某个类,实现某个接口。其实也就是我们前面所说的规范。好在JUnit目前得到了大多数软件工程师的认可。遵循JUnit我们会得到很多的支持。回归测试就是你不断地对所编写的代码进行测试:编写一些,测试一些,调试一些,然后循环这一过程,你会不断地重复先前的测试,哪怕你正编写其他的类,由于软件熵的存在,你可能在编写第五个类的时候发现,第五个类的某个操作会导致第二个类的测试失败。通过回归测试我们抓住了这条大Bug。
回归测试框架-JUnit
通过前面的介绍,我们对JUnit有了一个大概的轮廓。知道了它是干什么的。现在让我们动手改写上面的测试类testCar使其符合Junit的规范--能在JUnit中运行。
//执行测试的类(JUnit版)
import junit.work.*;
public class testCar extends TestCase {
protected int expectedWheels;
protected Car myCar;
public testCar(String name) {
super(name);
}
protected void setUp() {
expectedWheels = 4;
myCar = new Car();
}
public static Test suite() {
/*
* the type safe way
*
TestSuite suite= new TestSuite();
suite.addTest(
new testCar(“Car.getWheels“) {
protected void runTest() { testGetWheels(); }
}
);
return suite;
*/
/*
* the dynamic way
*/
return new TestSuite(testCar.class);
}
public void testGetWheels() {
assertEquals(expectedWheels, myCar.getWheels());
}
}
改版后的testCar已经面目全非。先让我们了解这些改动都是什么含义,再看如何执行这个测试。
1》import语句,引入JUnit的类。(没问题吧)
2》继承 TestCase 。可以暂时将一个TestCase看作是对某个类进行测试的方法的集合。详细介绍请参看JUnit资料
3》setUp()设定了进行初始化的任务。我们以后会看到setUp会有特别的用处。
4》testGetWheeels()对预期的值和myCar.getWheels()返回的值进行比较,并打印比较的结果。assertEquals是junit.work.Assert中所定义的方法,junit.work.TestCase继承了junit.work.Assert。
5》suite()是一个很特殊的静态方法。JUnit的TestRunner会调用suite方法来确定有多少个测试可以执行。上面的例子显示了两种方法:静态的方法是构造一个内部类,并利用构造函数给该测试命名(test name, 如 Car.getWheels ),其覆盖的runTest()方法,指明了该测试需要执行那些方法--testGetWheels()。动态的方法是利用内省(reflection )来实现runTest(),找出需要执行那些测试。此时测试的名字即是测试方法(test method,如testGetWheels)的名字。JUnit会自动找出并调用该类的测试方法。
6》将TestSuite看作是包裹测试的一个容器。如果将测试比作叶子节点的话,TestSuite就是分支节点。实际上TestCase,TestSuite以及TestSuite组成了一个composite Pattern。 JUnit的文档中有一篇专门讲解如何使用Pattern构造Junit框架。有兴趣的朋友可以查看JUnit资料。
如何运行该测试呢?手工的方法是键入如下命令:
[Windows] d:》java junit.textui.TestRunner testCar
[Unix] % java junit.textui.TestRunner testCar
别担心你要敲的字符量,以后在IDE中,只要点几下鼠标就成了。运行结果应该如下所示,表明执行了一个测试,并通过了测试:
.
Time: 0
OK (1 tests)
如果我们将Car.getWheels()中返回的的值修改为3,模拟出错的情形,则会得到如下结果:
.F
Time: 0
There was 1 failure:
1) testGetWheels(testCar)junit.work.AssertionFailedError: expected:《4》 but was:《3》
at testCar.testGetWheels(testCar.java:37)
FAILURES!!!
Tests run: 1, Failures: 1, Errors: 0
注意:Time上的小点表示测试个数,如果测试通过则显示OK。否则在小点的后边标上F,表示该测试失败。注意,在模拟出错的测试中,我们会得到详细的测试报告“expected:《4》 but was:《3》”,这足以告诉我们问题发生在何处。下面就是你调试,测试,调试,测试…的过程,直至得到期望的结果。
Design by Contract(这句话我没法翻译)
Design by Contract本是Bertrand Meyer(Eiffel语言的创始人)开发的一种设计技术。我发现在JUnit中使用Design by Contract会带来意想不到的效果。Design by Contract的核心是断言(assersion)。断言是一个布尔语句,该语句不能为假,如果为假,则表明出现了一个bug。Design by Contract使用三种断言:前置条件(pre-conditions)、后置条件(post-conditions)和不变式(invariants)这里不打算详细讨论Design by Contract的细节,而是希望其在测试中能发挥其作用。
前置条件在执行测试之前可以用于判断是否允许进入测试,即进入测试的条件。如 expectedWheels 》 0, myCar != null。后置条件用于在测试执行后判断测试的结果是否正确。如 expectedWheels==myCar.getWheels()。而不变式在判断交易(Transaction)的一致性(consistency)方面尤为有用。我希望JUnit可以将Design by Contract作为未来版本的一个增强。
Refactoring(这句话我依然没法翻译)
Refactoring本来与测试没有直接的联系,而是与软件熵有关,但既然我们说测试能解决软件熵问题,我们也就必须说出解决之道。(仅仅进行测试只能发现软件熵,Refactoring则可解决软件熵带来的问题。)软件熵引出了一个问题:是否需要重新设计整个软件的结构?理论上应该如此,但现实不允许我们这么做。这或者是由于时间的原因,或者是由于费用的原因。重新设计整个软件的结构会给我们带来短期的痛苦。而不停地给软件打补丁甚至是补丁的补丁则会给我们带来长期的痛苦。(不管怎样,我们总处于水深火热之中)
Refactoring是一个术语,用于描述一种技术,利用这种技术我们可以免于重构整个软件所带来的短期痛苦。当你refactor时,你并不改变程序的功能,而是改变程序内部的结构,使其更易理解和使用。如:该变一个方法的名字,将一个成员变量从一个类移到另一个类,将两个类似方法抽象到父类中。所作的每一个步都很小,然而1-2个小时的Refactoring工作可以使你的程序结构更适合目前的情况。Refactoring有一些规则:
1》 不要在加入新功能的同时refactor已有的代码。在这两者间要有一个清晰的界限。如每天早上1-2个小时的Refactoring,其余时间添加新的功能。
2》 在你开始Refactoring前,和Refactoring后都要保证测试能顺利通过。否则Refactoring没有任何意义。
3》 进行小的Refactoring,大的就不是Refactoring了。如果你打算重构整个软件,就没有必要Refactoring了。
只有在添加新功能和调试bug时才又必要Refactoring。不要等到交付软件的最后关头才Refactoring。那样和打补丁的区别不大。Refactoring 用在回归测试中也能显示其威力。要明白,我不反对打补丁,但要记住打补丁是应该最后使用的必杀绝招。(打补丁也需要很高的技术,详情参看微软网站)
IDE对JUnit的支持
目前支持JUnit的Java IDE 包括 IDE 方式 个人评价(1-5,满分5)
Forte for Java 3.0 Enterprise Edition plug-in 3
JBuilder 6 Enterprise Edition integrated with IDE 4
Visual Age for Java support N/A
在IDE中如何使用JUnit,是非常具体的事情。不同的IDE有不同的使用方法。一旦理解了JUnit的本质,使用起来就十分容易了。所以我们不依赖于具体的IDE,而是集中精力讲述如何利用JUnit编写单元测试代码。心急的人可参看资料。