junit在近十年以来一直是业界测试标准框架,作为一个java开发人员,你可以不知道hibernate或者spring,但是junit是必须知道的。要编写出质量良好的软件,测试是必不可少的,许多人从毕业或培训出来在工作中从事开发工作,但是甚少甚至不进行测试,这里指的就是单元测试,一方面是对软件测试没有一个正确的概念,另一方面是存粹的单元测试比较让人抗拒。junit解决了原始的单元测试麻烦却又没有技术性的重复工作,极大的提高了单元测试的效率。
上了规模的软件会由许多个功能单一的元件组成,这些元件称为工作单元,软件的可靠性就是从这些最小粒度的工作单元共同决定的,保证每个单元功能的正确,是软件质量的基本保证,一个有良好素养的程序员,应当在开发阶段编写单元测试,确保提交的代码正确无误,这是对工作团队和自身的负责。虽然人总会犯错,但是单元测试如果覆盖足够,就可以揪出很多想当然没有的bug,在修复的这些问题之后,你会对自己的代码带有信心,而不是每次集成测试你都得提心掉胆。
如何使用junit
1、将junit相关库加入项目构建路径,使用eclipse等IDE基本都会内置junit,无需自己下载类库。
//被测试的类
public class Demo1 {
public int number;
public Demo1(){
try {
//模拟初始化需要较长时间
Thread.sleep(2000);
System.out.println("创建Demo1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被测试的方法
public int add(int i){
return i++;
}
//被测试的方法
public int sub(int i){
return --i;
}
public int mul(int i){
return number*i;
}
//没有写好的方法
public int div(int i){
return 0;
}
}
2、如上图,创建对应的测试用例,并且最好放在叫test的目录下,具体看你的团队如何约定。如果是maven则相应的在test目录中。
3、当然,你自己手动创建也可以,如果是junit4则是一个普通类,如果junit3则需要继承testCase,还有类名以及方法名也有约定,比较麻烦,所以从junit4开始使用就可以了。
使用IDE提供的UI你可以勾选需要测试的类的方法即可自动生成测试方法还有注解。
自动创建好的测试类:
//静态的导入可以简化junit工具静态方法的调用
import static org.junit.Assert.*;
import org.junit.Test;
public class Demo1Test {
@Test//这是一个测试方法,必须是非静态void无参
public final void testAdd() {
fail("Not yet implemented"); //输出表示测试失败的结果和信息
}
@Test
public final void testSub() {
fail("Not yet implemented"); // TODO
}
@Test
public final void testMul() {
fail("Not yet implemented"); // TODO
}
@Test
public final void testDiv() {
fail("Not yet implemented"); // TODO
}
}
这个时候你可以先run as junit TEST看一下反应如何:
红色代表测试不通过,以及测试失败的方法,是不是很直观?光是这点比你使用main方法方便多了。
开始第一个测试
public class Demo1Test {
static Demo1 demo1;
/*@BeforeClass:
* 在所有测试方法执行之前执行一次,通常用于初始化一些耗时操作,如文件、网络、数据库等
* 例如这个demo1的创建耗时较多,如果在每个test方法里面去new,那么测试会变得很慢且冗余很多。
* note:不能重复,必须是静态void无参,父类方法会在子类执行之前执行
*/
@BeforeClass
public static void setUpBeforeClass() throws Exception {
demo1 = new Demo1();
}
/*@AfterClass:
* 在所有测试方法执行之后执行一次,通常用于一些收尾工作,如释放资源,输出资源等。
* note:不能重复,必须是静态void无参,父类方法会在子类执行之后执行
*/
@AfterClass
public static void tearDownAfterClass() throws Exception {
demo1 = null;
}
/*@Before:
* 在每个测试方法执行前都会执行一次,通常用于将测试数据初始化、复位等。
* note:不能重复,必须是非静态void无参
*/
@Before
public void setUp() {
demo1.number = 2;
}
/*@After:
* 在每个测试方法执行后都会执行一次,通常用于将测试数据初始化、复位等。
* note:不能重复,必须是非静态void无参
*/
@After
public void tearDown() {
System.out.println(demo1.number);
}
@Test
public final void testAdd() {
assertEquals(5, demo1.add(4));//断言测试结果,期望值5,如果实际值不相等,则结果为失败不通过。
}
@Test
public final void testSub() {
assertEquals(4, demo1.sub(5));
}
@Test
public final void testMul() {
assertEquals(10, demo1.mul(5));
}
/*@Ignore:
* 标识该方法忽略测试,可能处于某种原因(方法为实现,问题未解决等)
*/
@Ignore
@Test
public final void testDiv() {
assertEquals(5, demo1.div(10));
}
}
测试总结果不通过,一个忽略,一个失败,两个通过。
从输出结果来看也验证了注释的说明:
进一步的测试
public class Demo2 {
public void doSomeThing(){
for(;;){}
}
public void parseString(String i){
int num = Integer.parseInt(i);
}
public String concat(String str1,String str2){
return str1+str2;
}
}
public class Demo2Test {
/*timeout:
* 超过该阈值则认为测试超时不用过,通常用于测试处理耗时,死循环等
*/
@Test(timeout=1000)
public final void testDoSomeThing() {
new Demo2().doSomeThing();
}
/*expected:
* 期望测试过程会出现的异常,通常和输出参数配合,如果没有得到期望的异常则不通过
*/
@Test(expected=NumberFormatException.class)
public final void testParseString() {
new Demo2().parseString("java8");
}
/*
* 通常建议一般的测试方法为final,这样可以避免子类重写出错,或者重复的测试方法,
* 另外,通过编写测试方法,我们发现如果被测方法没有返回值,会对测试带来很大不便,
* 所以,这可以反过来指导我们编写方法都提供返回值,如果一个被测试类里面几个核心的方法都没有返回值,
* 那么,你需要检查被测试方法是否粒度过大,或者违背单一职责。
*/
@Test
public final void testConcat() {
String str = new Demo2().concat("BL", " UE");
assertEquals("BLUE", str);
}
}
parseString测试抛出了预期的异常,通过,DoSomeThing测试由于存在死循环导致超时,不通过,Concat经测试存在bug,如果参数有空格就不符合预期。
参数化测试
当需要进行多组参数测试的时候,如上面的concat,你可能需要编写多个测试方法应对不同的输入,这显然很不人性化,因此,使用参数化测试来做。
我们对测试类进行修改:
/*@RunWith:
* junit实际上根据runner来决定采用哪种测试策略来执行测试方法,如果没有显示注解声明,
* 则会使用默认的runner,如果我们要使用参数化的测试模式,那么需要显示的定义使用的runner
*/
@RunWith(Parameterized.class)
public class Demo2Test {
/*@Parameters:
* 当使用参数化测试的时候,通过该注解标注返回(结果:参数)的集合,
* 这些键值对会被输入到测试方法自动进行断言
* note:必须是静态无参的,返回类型是数组或集合,每换一组参数都会执行所有的测试方法,因此你最好有所准备
*/
@Parameters
public static Object[] testData(){
return new Object[][]{{"BLUE","BL"," UE"},
{"Google","Goog","le"},
{"Anna"," An","na"}};
}
//定义参数成员变量
String result;
String param1;
String param2;
//提供构造方法设置参数,注意参数顺序,顺序是按着上面的数组中元素的顺序赋值的
public Demo2Test(String result,String param1, String param2) {
this.result = result;
this.param1 = param1;
this.param2 = param2;
}
@Test
public final void testConcat() {
String str = new Demo2().concat(param1, param2);
assertEquals(result, str);
}
打包测试
实际中团队开发,我们不太可能一个个测试类去点击执行,打包测试是一种效率更高的办法,将需要执行的测试类批量的执行。但是我觉得这个套件比较鸡肋,没有达到所谓的打包效果,因为注解的值只能是类型的字面值,不能通过工具来转换,也就限制了运用,特别是多个包中的多个测试类的情况下很不方便,可以再考虑使用ant或maven工具来做。
注意runner
@RunWith(Suite.class)
@SuiteClasses({Demo1Test.class,Demo2Test.class})
public class TestAll {
}