什么是单元测试?
单元测试
单元测试是针对最小的功能单元编写测试代码,而Java程序中最小的功能单元是方法,单元测试就是针对单个java方法的测试。
单元测试的优势
- 确保单个方法运行正常
- 如果修改了某个方法代码,只需确保对应的单元测试通过即可
- 测试代码本身可以作为示例代码
- 可以自动化运行所有测试并获得报告
TDD测试驱动开发
测试驱动开发(Test-Driven Development)
测试驱动开发是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。
为什么要使用Junit?
通常情况下,当我们写好了一种方法之后要确认方法的正确性时,我们会在main函数中设计一个测试用例,并且调用方法测试,检测方法是否正确。使用main方法进行测试就代表着每写一个方法就要在main方法内写一个测试代码,然后测试下一个方法时又要注释或删掉上一个方法的测试代码,这样就极其不方便。
比如在类中有加,减两种方法
public class TestJunit {
public static int addNums(int a,int b){//为了在main方法内调用方便设为static,后续测试没有使用static修饰加减两种方法
return a+b;
}
public static int minusNums(int a,int b){
return a-b;
}
}
此时对加进行测试,将1+5=6作为测试用例
public static void main(String[] args) {
int test1=addNums(1,5);//6,正确
System.out.println(test1);
}
那么再对减进行测试,1-5=-4作为测试用例,就需要注释掉上一步的内容
public static void main(String[] args) {
//int test1=addNums(1,5);//6,正确
//System.out.println(test1);
int test2=minusNums(1,5);//-4,正确
System.out.println(test2);
}
这样很不方便,如果通过Junit进行测试就不需要重复的写测试代码再注释掉测试代码了
使用Junit的步骤
-
在项目中建立测试类
-
在测试类中输入 @Test注解,刚开始注解会报红,此时只需要在系统提示的操作下导入Junit的包即可
-
编写测试类
public class TestDemo { @Test public void Test1(){ TestJunit testJunit = new TestJunit(); int ans=testJunit.addNums(1,5); System.out.println(ans); } @Test public void Test2(){ TestJunit testJunit=new TestJunit(); int ans=testJunit.minusNums(1,5); System.out.println(ans); } }
-
运行测试类
-
此时两个测试用例成功运行,并输出正确结果
断言
Junit提供了辅助函数去帮助我们验证预期结果和实际结果是否相同,这种辅助函数称为断言
断言 | 参数解释 |
---|---|
assertEquals([String message],expected,actual) | message是可选的参数,如果报错会报告这个message expected是期望值,通常是用户指定的内容,actual是测试代码返回的实际值 该断言用于判断预期值和实际值是否相等 |
assertEquals([String message],expected,actual,tolerance) | message是可选的参数,如果报错会报告这个message expected是期望值,通常是用户指定的内容,actual是测试代码返回的实际值 tolerance是误差参数,参加比较的两个数在这个误差内被认为是相等的 该断言用于判断预期值与实际值是否在规定的误差区间内,是则认为相等 |
assertTrue ([String message],Boolean condition) | message是可选的参数,如果报错会报告这个message condition是待验证的布尔值 该断言用来验证给定的布尔值是否为真,假如结果为假则验证失败 |
assertFalse([String message],Boolean condition) | message是可选的参数,如果报错会报告这个message condition是待验证的布尔值 该断言用来验证给定的布尔值是否为假,假如结果为真则验证失败 |
assertNull([String message],Object object) | message是可选的参数,如果报错会报告这个message object是待验证的对象 该断言用来验证给定的对象是否为Null,假如不为Null则验证失败 |
assertNotNull([String message],Object object) | message是可选的参数,如果报错会报告这个message object是待验证的对象 该断言用来验证给定的对象是否不为Null,假如为Null则验证失败 |
assertSame ([String message], expected,actual) | message是可选的参数,如果报错会报告这个message expect是期望值,actual是测试代码返回的实际值 该断言用来验证expected和actual参数所引用的是否是同一个对象,假如不是同一个对象则验证失败。 |
assertNotSame ([String message], expected,actual) | message是可选的参数,如果报错会报告这个message expect是期望值,actual是测试代码返回的实际值 该断言用来验证expected和actual参数所引用的是否是不同对象,假如是同一个对象则验证失败。 |
Fail([String message]) | message是可选的参数,如果报错会报告这个message 该断言用来使测试失败,通常用在测试不能到达的分支上 |
注解
注解名 | 用途 |
---|---|
@Test | 表示该方法是一个测试方法 测试方法必须为public void |
@Before | 在运行多个测试方法时,可能会在数据准备或其他准备中执行一段相同的代码,那么可以将这段可公用的代码提取出来,作为一个方法a并注解@Before,@Before和@BeForeClass的区别是每个测试方法运行前都执行一次这个方法a ,该方法必须为public void,不能为static;比如初始化测试对象 input=new FileInputStream(); |
@BeforeClass | 在运行多个测试方法时,可能会在数据准备或其他准备中执行一段相同的代码,那么可以将这段可公用的代码提取出来,作为一个方法a并注解@BeforeClass,那么在所有测试方法运行前会先运行一次这个方法a,该方法必须是public static void;一般是初始化非常耗时的资源,比如创建数据库 |
@After | 与@Before类似,主要用于注解运行测试类之后再运行的方法,比如销毁@Before创建的测试对象 input.close() |
@AfterClass | 与@BeforeClass类似,但是顺序不同,比如删除数据库 |
@Runwith | 放在测试类名前,用来确定这个类怎么运行,指定测试运行器。 |
@Ignore | 暂不运行某些测试方法或类,在测试方法前加上@Ignore注解,Junit会统计Ignore次数 |
@Parameters | 用于参数化功能 |
使用注解和断言之后的代码为
public class TestDemo {
TestJunit testJunit;//定义测试对象
@Before
public void beforeMethod(){
testJunit=new TestJunit();//初始化测试对象
System.out.println("before");
}
@Test
public void Test1(){
int ans=testJunit.addNums(1,5);
Assert.assertEquals("ErrorTest1",6,ans);
}
@Test
public void Test2(){
int ans=testJunit.minusNums(1,5);
Assert.assertEquals("ErrorTest2",-4,ans);
}
@After
public void afterMethod(){
System.out.println("after");
}
}
运行结果
如果修改代码,让Test1的Assert.assertEquals(“ErrorTest1”,6,ans);预期结果为7,此时预期结果是错误的,实际结果6是对的,但是他们不相同,测试就会报错
可以看到虽然Test1出现错误,但是Test2是没有受影响的,并且很清楚的告诉我们哪里出现的错误。
注意:如果预期与实际值相同,是不会输出message的内容的
参数化测试
参数化测试可以使用不同的值反复运行同一个测试
-
创建一个判断是否是偶数的方法
public boolean isEven(int i){ return i%2==0?true:false; }
-
在测试类上注解@RunWith(Parameterized.class)
@RunWith(Parameterized.class) public class TestDemo {}
-
创建@Parameters注释的公共静态方法,返回一个对象的集合来作为测试数据集合
@Parameterized.Parameters public static Collection Numbers(){ return Arrays.asList(new Object[][]{ {1,false}, {2,true}, {12,true}, {18,true}, {35,false}, {36,true} }); }
-
创建一个公共的构造函数,它接收和一行测试数据相等同的东西
-
为每一列测试数据创建一个实例变量
-
用实例变量作为测试数据来源来创建测试用例
代码:
@RunWith(Parameterized.class)
public class TestDemo {
TestJunit testJunit;//定义测试对象
private Boolean expected;//定义预期的布尔值
private Integer numbers;//定义输入的数据
@Before
public void beforeMethod(){
testJunit=new TestJunit();//初始化测试对象
System.out.println("before");
}
public TestDemo(Integer numbers,Boolean expected){//构造函数
this.numbers=numbers;
this.expected=expected;
}
@Parameterized.Parameters
public static Collection Numbers(){//测试所需数据集
return Arrays.asList(new Object[][]{
{1,false},
{2,true},
{12,true},
{18,true},
{35,false},
{36,true}
});
}
@Test
public void TestisEven(){//测试方法
boolean ans=testJunit.isEven(numbers);
Assert.assertEquals(expected,ans);
}
@After
public void afterMethod(){
System.out.println("after");
}
}
可以看到每一组数据都运行正常且每组测试的运行都会执行before和after
超时测试
@Test(timeout=1000),表示超时时间为1000毫秒,timeout的单位为毫秒
@Test(timeout = 1000)