ivaneye.com

读源码-JUnit3使用

JUnit简介

JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个。

JUnit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。

场景

假设,我们有一个Person类

代码如下:

package org.ivan;
public class Person {
    public String say(String name) {
        return "Hello," + name;
    }
    public String sayHi(String name) {
        return "Hi," + name;
    }
}

如何保证方法返回了正确的值?

当然,你可以使用main方法,代码如下:

public static void main(String[] args){
  Person p = new Person();
  System.out.println("test Person.say" + ("Hello,Ivan".equals(p.say("Ivan"))));
  System.out.println("test Person.sayHi" + ("Hi,Ivan".equals(p.sayHi("Ivan"))));
}

但是这样有几个问题:

JUnit解决方案

针对如上几个问题,JUnit提供了如下解决方案:

引入JUnit

项目使用Maven管理,所以引入JUnit只需要在pom中添加对应的dependency即可

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>3.8.2</version>
  <scope>test</scope>
</dependency>

编写测试类

对于Maven来说,源代码存放在project-name/src/main/java目录下。而对应的测试类在project-name/src/test/java目录下.

package org.ivan;
import junit.framework.TestCase;
public class PersonTest extends TestCase {
    public void testSay() throws Exception {
        Person person = new Person();
        assertEquals("Hello,Ivan", person.say("Ivan"));
    }
    public void testSayHi() throws Exception {
        Person person = new Person();
        assertEquals("Hi,Ivan", person.sayHi("Ivan"));
    }
}

运行测试

Intellij IDEA运行结果显示:

Maven运行结果:

Running org.ivan.PersonTest
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec

命令行输出结果:

Time: 0.015
OK (2 tests)

测试失败

我们修改PersonTest类,来使测试失败,看下失败时JUnit如何提示.

package org.ivan;
import junit.framework.TestCase;
public class PersonTest extends TestCase {
    public void testSay() throws Exception {
        Person person = new Person();
        assertEquals("Hi,Ivan", person.say("Ivan")); //not equals
    }
    public void testSayHi() throws Exception {
        Person person = new Person();
        assertEquals("Hi,Ivan", person.sayHi("Ivan"));
    }
}

Intellij IDEA运行结果显示:

Maven运行结果:

Running org.ivan.PersonTest
Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0 sec <<< FAILURE!
testSay(org.ivan.PersonTest)  Time elapsed: 0 sec  <<< FAILURE!
junit.framework.ComparisonFailure: expected:<H[i],Ivan> but was:<H[ello],Ivan>
        at junit.framework.Assert.assertEquals(Assert.java:81)
        at junit.framework.Assert.assertEquals(Assert.java:87)
        at org.ivan.PersonTest.testSay(PersonTest.java:22)

命令行输出结果:

Time: 0.005
There was 1 failure:
1) testSay(org.ivan.PersonTest)junit.framework.ComparisonFailure: expected:<H[i],Ivan> but was:<H[ello],Ivan>
    at org.ivan.PersonTest.testSay(PersonTest.java:22)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
FAILURES!!!
Tests run: 2,  Failures: 1,  Errors: 0

重构代码

细心的你应该看到了,在PersonTest的测试方法中,都是先实例化了一个Person类,然后对其方法进行调用,如果有100个方法,那就需要实例化100个Person类,足足重复写了100遍代码。

JUnit提供了对应的方法,可以简化操作。

package org.ivan;
import junit.framework.TestCase;
public class PersonTest extends TestCase {
    private Person person;
    public void setUp() throws Exception {
        System.out.println("setUp invoke");
        person = new Person();
    }
    public void tearDown() throws Exception {
        System.out.println("tearDown invoke");
    }
    public void testSay() throws Exception {
        assertEquals("Hi,Ivan", person.say("Ivan"));
    }
    public void testSayHi() throws Exception {
        assertEquals("Hi,Ivan", person.sayHi("Ivan"));
    }
}

运行多个测试类

假设我们新增一个类Animal:

package org.ivan;
public class Animal {
    public String say(String name) {
        return "momo," + name;
    }
    public String sayHi(String name) {
        return "gigi," + name;
    }
}

以及对应的测试类:

package org.ivan;
import junit.framework.TestCase;
public class AnimalTest extends TestCase {
    private Animal animal;
    public void setUp() throws Exception {
        System.out.println("setUp invoke");
        animal = new Animal();
    }
    public void tearDown() throws Exception {
        System.out.println("tearDown invoke");
    }
    public void testSay() throws Exception {
        assertEquals("momo,Ivan", animal.say("Ivan"));
    }
    public void testSayHi() throws Exception {
        assertEquals("gigi,Ivan", animal.sayHi("Ivan"));
    }
}

如果我们想同时运行PersonTest和AnimalTest,该如何做?

package org.ivan;
import junit.framework.Test;
import junit.framework.TestSuite;
public class TestAll {
    public static Test suite() {
        TestSuite suite = new TestSuite("测试所有");
        suite.addTest(new TestSuite(PersonTest.class));
        suite.addTest(new TestSuite(AnimalTest.class));
        return suite;
    }
}

BeforeClass,AfterClass

上面提到的setUp()和tearDown()方法是在每次测试方法前后被调用,假如你希望:

该怎么办?

JUnit3可以通过TestSetup类,来实现此需求。

package org.ivan;
import junit.extensions.TestSetup;
import junit.framework.Test;
import junit.framework.TestSuite;
public class TestAll {
    public static Test suite() {
        TestSuite suite = new TestSuite("测试所有");
        ...
        suite.addTest(new TestSetup(new TestSuite(AnimalTest.class)){
            public void setUp() throws Exception {
                System.out.println(" Global setUp 2");
            }
            public void tearDown() throws Exception {
                System.out.println(" Global tearDown 2");
            }
        });
        return suite;
    }
}

在TestSetup中实现的setUp()和tearDown()方法只会被执行一次。

重复测试

假设需要对某个测试进行多次测试,该怎么办?

JUnit3中提供了RepeatedTest。

package org.ivan;
import junit.extensions.RepeatedTest;
import junit.framework.Test;
import junit.framework.TestSuite;
public class TestAll {
    public static Test suite() {
        TestSuite suite = new TestSuite("测试所有");
        ...
        suite.addTest(new RepeatedTest(new TestSuite(AnimalTest.class),3));
        ...
        return suite;
    }
}

UML