什么是单元测试
写了个类,要给别人用,会不会有bug?怎么办?测试一下。
用main方法测试好不好?不好!
1. 不能一起运行!
2. 大多数情况下需要人为的观察输出确定是否正确
为什么要进行单元测试
重用测试,应付将来的实现的变化。
提高士气,明确知道我的东西是没问题的。
JUnit4 HelloWorld
1. new project
2. 建立类
3. 建立testcase
放弃旧的断言,使用hamcrest断言
1. assertThat(import static org.junit.Assert.*;)
2. 使用hamcrest的匹配方法(import static org.hamcrest.Matchers.*;),语法更自然
3. 示例
a)
assertThat( n, allOf( greaterThan(1), lessThan(15) ) );
assertThat( n, anyOf( greaterThan(16), lessThan(8) ) );
assertThat( n, anything() );
assertThat( str, is( "bjsxt" ) );
assertThat( str, not( "bjxxt" ) );
b)
assertThat( str, containsString( "bjsxt" ) );
assertThat( str, endsWith("bjsxt" ) );
assertThat( str, startsWith( "bjsxt" ) );
assertThat( n, equalTo( nExpected ) );
assertThat( str, equalToIgnoringCase( "bjsxt" ) );
assertThat( str, equalToIgnoringWhiteSpace( "bjsxt" ) );
c)
assertThat( d, closeTo( 3.0, 0.3 ) );//3+-0.3
assertThat( d, greaterThan(3.0) );
assertThat( d, lessThan (10.0) );
assertThat( d, greaterThanOrEqualTo (5.0) );
assertThat( d, lessThanOrEqualTo (16.0) );
d)
assertThat( map, hasEntry( "bjsxt", "bjsxt" ) );
assertThat( iterable, hasItem ( "bjsxt" ) );
assertThat( map, hasKey ( "bjsxt" ) );
assertThat( map, hasValue ( "bjsxt" ) );
Failure和Error
1. Failure是指测试失败,测试没有通过要求
2. Error是指测试程序代码本身出错
JUnit4 Annotation
1. @Test: 测试方法
a) (expected=XXException.class)
b) (timeout=xxx),xxx的单位为毫秒
2. @Ignore: 被忽略的测试方法,当某一次测试中不需要或者不能测试某个方法,加这个注解让它在本次测试中不运行,也可以放在测试类上,表示这个测试类中的所有测试方法都不运行。
3. @Before: 同一个测试类中每一个测试方法之前运行
4. @After: 同一个测试类中每一个测试方法之后运行
5. @BeforeClass: 同一个测试类中所有测试开始之前运行,方法必须是static
6. @AfterClass: 同一个测试类中所有测试结束之后运行,方法必须是static
运行多个测试
右键测试类所在的包->Run As->Run Configurations->在"Run all tests in the selected project, package or source folder:"中选要测试的项目的名称,然后点Run
注意
1. 遵守约定,比如:
a) 类放在test包中
b) 类名用XXXTest结尾,如UserTest
c) 方法用testMethod命名,如testAdd
其他框架
TestNG
JUnit4与JUnit3.8相比,多的一个功能就是参数化测试,即为某个测试方法提供几组测试数据来进行测试。例:
//目标类
package com.paul.junit4;
public class Calculator {
public int add(int x, int y) {
return x + y;
}
}
//测试类
package com.paul.junit4;
import java.util.Arrays;
import java.util.Collection;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
@RunWith(Parameterized.class) //表示不使用默认的测试运行器,而是使用参数化运行器(Parameterized.class)。类中的写法是固定的,需要一个提供数据的静态方法,用@Parameters注解,还要提供构造方法,这是由JUnit在运行时自动调用的,从而将测试数据赋给成员变量来执行测试。
public class CalculatorParametersTest {
private int expected;
private int linput;
private int rinput;
private Calculator cal;
public CalculatorParametersTest(int expected, int linput, int rinput) {
this.expected = expected;
this.linput = linput;
this.rinput = rinput;
}
@SuppressWarnings("unchecked")
@Parameters
public static Collection prepareData() {
Object[][] array = {{8, 3, 5}, {4, 1, 3}, {4, -4, 8}, {0, -4, 4}};
return Arrays.asList(array);
}
@Before
public void setUp() throws Exception {
cal = new Calculator();
}
@After
public void tearDown() throws Exception {
}
@Test
public void testAdd() {
assertThat(this.expected, is(cal.add(this.linput, this.rinput)));
}
}
JUnit4没有测试套件的概念,而用的是测试套件运行器(Suite.class),例:
@RunWith(Suite.class)
@Suite.SuiteClasses({CalculatorTest.class, LargestTest.class, ParametersTest.class})
public class TestAll {
}
@Suite.SuiteClasses中还可以继续指定Suite类,这样可以将多个Suite组合起来,JUnit会一直查找Suite中的所有的测试类,如:
@RunWith(Suite.class)
@Suite.SuiteClasses(TestAll.class)
public class TestAll2 {
}
测试类TestAll只是一个空的类,主要是用SuiteClasses注解来表示要一起运行的测试类有哪些。
以下是JUnit3.8的一些知识总结:
1、用命令行方式启动运行JUnit的方式:
public static void main(String[] args) {
junit.textui.TestRunner.run(TTest.class);
//junit.awtui.TestRunner.run(TTest.class); 此方法可能只适用于JUnit3.8
}
在测试类TTest中写一个main方法,运行run方法,run方法中的类必须继承了TestCase,
所以我觉得这种方法只适用于JUnit3.8。
2、JUnit3.8中有TestSuite的概念,即将多个继承了TestCase的测试类加入到TestSuite中,
达到统一执行被加入的测试类的目的。例:
public class TestAll extends TestCase {
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTestSuite(TTest.class);
suite.addTestSuite(UserTest.class);
return suite;
}
}
其中,TTest和UserTest都是继承了TestCase的测试类。
TestAll本身也是一个测试类。只是用来统一执行已经写好了的别的测试类。
3、假如有一个目标类中有一个private访问类型的方法需要单元测试,但我们又不能修改其访问类型为public,
这时只能用反射来获取该方法并调用。例:
目标类:
package com.paul.junit4;
public class Calculator {
private int add(int x, int y) {
return x + y;
}
}
测试类:
package com.paul.junit4;
import java.lang.reflect.Method;
import org.junit.Assert;
import org.junit.Test;
public class CalculatorTest {
@Test
public void testAdd() {
Class<Calculator> clazz = Calculator.class;
Calculator cal = new Calculator();
Method m = null;
try {
m = clazz.getDeclaredMethod("add", new Class[]{Integer.TYPE, Integer.TYPE});
} catch (Exception e) {
e.printStackTrace();
}
m.setAccessible(true);
Object result = "";
try {
result = m.invoke(cal, new Object[]{2, 3});
} catch (Exception e) {
e.printStackTrace();
}
Assert.assertEquals(5, result);
/*System.out.println(Integer.class == Integer.TYPE);
System.out.println(Integer.class);
System.out.println(Integer.TYPE);
System.out.println(int.class);*/
}
}
或用JUnit4的写法:
package com.paul.junit4;
import java.lang.reflect.Method;
import static org.junit.Assert.*;
import org.junit.Test;
import static org.hamcrest.Matchers.*;
public class CalculatorTest {
@Test
public void testAdd() {
Class<Calculator> clazz = Calculator.class;
Calculator cal = new Calculator();
Method m = null;
try {
m = clazz.getDeclaredMethod("add", new Class[]{Integer.TYPE, Integer.TYPE});
} catch (Exception e) {
e.printStackTrace();
}
m.setAccessible(true);
int result = 0;
try {
result = (Integer)m.invoke(cal, new Object[]{2, 3});
} catch (Exception e) {
e.printStackTrace();
}
// Assert.assertEquals(5, result);
assertThat(result, is(5));
/*System.out.println(Integer.class == Integer.TYPE);
System.out.println(Integer.class);
System.out.println(Integer.TYPE);
System.out.println(int.class);*/
}
}
4、如果想设定test case的执行次数,可以用RepeatedTest类。例:
package com.paul.junit4;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import junit.framework.TestCase;
import com.paul.junit4.User;
public class UserTest extends TestCase {
//测试类必须加这个构造方法,以便传入测试方法
public UserTest(String name) {
super(name);
}
public void testGetName() {
assertThat(new User().getName(), equalTo("宋慧乔"));
}
}
package com.paul.junit4;
import junit.extensions.RepeatedTest;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
public class TestAll extends TestCase {
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTestSuite(TTest.class);
suite.addTestSuite(UserTest.class);
//20为重复执行的次数,UserTest测试类必须有一个String name的构造方法来传入要调用的测试方法名称
suite.addTest(new RepeatedTest(new UserTest("testGetName"), 20)); return suite;
}
}