读源码-JUnit3使用
JUnit简介
JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个。
JUnit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。
场景
假设,我们有一个Person类
- 包含两个方法say和sayHi
- 两个方法都接收一个String类型的参数
- say返回"Hello,"+arg
- sayHi返回"Hi,"+arg
代码如下:
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"))));
}
但是这样有几个问题:
- main方法可以放在任何位置,不方便管理(可以通过规范来限制)
- 类中所有需要测试的方法都在main中,如果方法很多,导致main方法很大,难以阅读
- 如果要测试多个类,你需要多个main方法,且需要手动去一个个调用
- 测试结果不直观,你需要一个个的看输出结果,才能确定方法是否都测试通过
JUnit解决方案
针对如上几个问题,JUnit提供了如下解决方案:
- 测试代码在独立的ClassPath下
- 每个方法有一个对应的测试方法,可以运行单个测试方法,也可以运行多个测试方法
- 每一个类有一个对应的测试类,可以运行多个测试类,也可以运行单个测试类
- 提供多种测试结果反馈,最著名的当然是绿条了。即当所有测试通过后,会显示一个绿条
引入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"));
}
}
- 在JUnit3中,测试类需要继承TestCase
- 测试方法需要以test开头,比如这里的testSay(),testSayHi()
- 方法中,实际上就是对Person的调用
- assertEquals用来比较,方法调用结果是否与期望相同
运行测试
- 如果使用IDE则可以直接在测试类上,右击来运行测试,如果测试成功会出现绿条
- 通过mvn test来运行测试,输出的是文字测试结果
- 通过java junit.textui.TestRunner org.ivan.PersonTest来运行
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"));
}
}
- 在每次调用test*()方法前,会调用setUp()方法
- 在每次调用test*()方法后,会调用tearDown()方法
运行多个测试类
假设我们新增一个类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,该如何做?
- 对于IDE,直接在测试包上右击,运行测试即可
- 对于Maven,依然使用mvn test命令
- 对于命令行来说,则需要新建一个类
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;
}
}
- TestAll类有一个静态的suite方法,返回类型为Test
- 其中向TestSuite类中添加需要测试的类
- 返回TestSuite
- 运行java junit.textui.TestRunner org.ivan.TestAll
BeforeClass,AfterClass
上面提到的setUp()和tearDown()方法是在每次测试方法前后被调用,假如你希望:
- 运行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;
}
}
- 这里就是对AnimalTest中的测试,重复执行3次