Junit单元测试

 一、JUnit单元测试

 单元测试又称模快测试,属于白金测试,是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。

 1.1 JUnit概述

 1.1.1 JUnit简介

 JUnit是用于编写可复用测试集的简单框架,是xUnit的一个子集。

          xUnit是一套基于测试驱动开发的测试框架,有PythonUnit、CppUnit、JUnit等。

JUnit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(what)的功能。
多数]ava的开发环境都已经集成了JUnit作为单元测试的工具,比如IDEA,Eclipse等等 

 1.1.2Junit优点

 Junit是在极限编程和重构(refactor)中被极力推荐使用的工具,因为在实现自动单元测试的情况下可以大大的提高开发的效率,但是实际上编写测试代码也是需要耗费很多的时间和精力的,那么使用这个东西好处到底在哪里呢?

  •  极限编程:要求在编写代码之前先写测试,这样可以强制你在写代码之前好好的思考代码(方法)的功能和逻辑,否则编写的代码很不稳定,那么你需要同时维护测试代码和实际代码,这个工作量就会大大增加,因此在极限编程中,基本过程是这样的:构思->编写测试代码->编写代码->测试,而且编写测试和编写代码都是增量式的,写一点测一点,在编写以后的代码中如果发现问题可以较快的追踪到问题的原因,减小回归错误的纠错难度。
  • 重构:其好处和极限编程中是类似的,因为重构也是要求改一点测一点,减少回归错误造成的时间消耗。
  • 其他情况:我们在开发的时候使用JUnit写一些适当的测试也是有必要的,因为一般我们也是需要编写测试的代码的,可能原来不是使用的JUnit,如果使用JUnit,而且针对接口(方法)编写测试代码会减少以后的维护工作,例如以后对方法内部的修改(这个就是相当于重构的工作了)。另外就是因为JUnit有断言功能,如果测试结果不通过会告诉我们哪个测试不通过,为什么,而如果是像以前的一般做法是写一些测试代码看其输出结果,然后再由自己来判断结果是否正确,使用JUnit的好处就是这个结果是否正确的判断是它来完成的,我们只需要看看它告诉我们结果是否正确就可以了,在一般情况下会大大提高效率。 

1.1.3 为什么要用JUnit

 1.测试框架可以帮助我们对编写的程序进行有目的地测试,帮助我们最大限度地避免代码中的bug,以保证系统的正确性和稳定性,。很多人对自己写的代码,测试时就简单写main,然后sysout输出控制台观察结果。这样非常枯燥繁琐,不规范。缺点:测试方法不能一起运行,测试结果要程序猿自己观察才可以判断程序逻辑是否正确。
2.JUnit的断言机制,可以直接将我们的预期结果和程序运行的结果进行一个比对,确保对结果的可预知性。

1.编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设;
2.可以将断言看作是异常处理的一种高级形式:
3.断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真。

 1.2 JUnit安装

JUnit官网:http://junit.org/
JUnit的jar包下载:https://github.com/jUnit-team/Unit/wiki/Download-and-instal
如果你安装的开发工具是IDEA,这就很好办,因为IDEA自带JUnit

1.3 简单例子快速入门

 新建一个项目工程,点击 文件File - 新建New - 项目Project,我这里项目名使用 Junitproj ,点击完成;

 1.3.1 创建简单业务类

 编写一个简单的计算类:Calculate类

public class Calculate {
    /**
     * 实现加减乘除的简单计算类
     */
    public int add(int a,int b) {
        return a+b;
    }
    public int subtract(int a,int b) {
        return a-b;
    }
    public int multiply(int a,int b) {
        return a*b;
    }
    public int divide(int a,int b) {
        return a/b;
    }
}

 1.3.2 创建简单业务类的测试类

 编写一个简单的测试类:CalculateTest类

public class CalculateTest {

	@Test
	public void testAdd() {
		Calculate calculate = new Calculate( ); 
		int result = calculate.add(1,3);
		/**
		 * 写断言:新言结果是否符合预期
		 *  expected 期望的
		 *  如果期望的结参数值相等,则测试成功;否则测试失败;	
		 *  assertEquals(4,result);
		 */	 	
		assertEquals("加法运算有问题",4,result);
	}

	@Test
	public void testSubtract() {
		Calculate calculate = new Calculate();
		int result = calculate.subtract(12,2);
		assertEquals("减法运算有问题",4,result);
		
		//故意设置减法期望值为4
	}
	@Test
	public void testMultiply() {
		Calculate calculate=new Calculate();
		int result = calculate.multiply(2,3);
		assertEquals( "乘法有问题",6,result);

	}
	@Test
	public void testDivide() {
		Calculate calculate =new Calculate();
		int result =calculate.divide(6,3);
		assertEquals( "除法有问题",2,result);
	}
}

 总共有4个测试方法,运行了4个方法;其中falled有1个,即有一个方法的输出结果跟我们的预期不一样。

1.4 JUnit使用注意点及测试失败的两种情况

1.4.1 JUnit使用的最佳实践 

1.测试方法上必须使用@Test进行修饰(只有添加@Test,才是测试方法,测试的时候才会运行);
2.测试方法必须使用public void 进行修饰,不能带任何的参数:
3.新建一个测试代码目录来存放我们的测试代码,即将测试代码和项目业务代码分开(不要在src下放测试代码);
4.测试类所在的包名应该和被测试类所在的包名保持一致;
5.测试单元中的每个方法必须可以独立测试,测试方法间不能有任何的依赖;
6.测试类使用Test作为类名的后缀(不是必须);
7.测试方法使用test作为方法名的前缀(不是必须); 

1.4.2 测试失败的两种情况 

注意:测试用例是用来达到测试想要的预期结果,而不能测试出程序的逻辑错误。
比如:你需要写一个计算长方形面积的方法,而你错误地认为周长的公式就是计算面积的。所以在测试方法中,就算结果达到了你的预期,但这显然不是正确的计算面积方法。 

新建测试类ErrorAndFailureTest

public class ErrorAndFailureTest {

	@Test
	public void testAdd() {
		int result =new Calculate().add(3, 3);// 预期值与程序输出不一样
		Assert.assertEquals("加法有问题",5,result);
	}

	@Test
	public void testDivide() {
		int result = new Calculate().divide(6,0);// 除法中,除数为0
		Assert.assertEquals("除法有问题",3,result);
	}

}

运行结果

testAdd()方法是failure(失败/故障)错误:

 testDivide()方法是error 错误:

说明:

  • Failure一般由单元测试使用的断言方法判断失败所引起的,这表示测试点发现了问题,就是说程序输出的结果和我们预期的不一样。
  • Error是由代码异常引起的,它可以产生于测试代码本身的错误,也可以是被测试代码中的一个隐藏的bug。

1.5 运行流程及常用注解 

 1.5.1 JUnit的运行流程

新建测试类 

 右键被测试类,新建一个测试类。弹出框中,首先改变测试类所在的代码目录,然后勾选4个方法:

 

public class CalculateTest2 {

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		System.out .println("this is setUpBefore class()...");
	}

	@AfterClass
	public static void tearDownAfterClass() throws Exception {
		System.out.println("this is tearDownAfterclass(...");
	}

	@Before
	public void setUp() throws Exception {
		System.out.println("this is setup() @Before");
	}

	@After
	public void tearDown() throws Exception {
		System.out.println("this is setup() @After");
	}

	@Test
	public void testAdd() {
		System.out.println("this is add()");
	}

	@Test
	public void testSubtract() {
		System.out.println("this is Subtract()");
	}

	@Test
	public void testMultiply() {
		System.out.println("this is multiply()");
	}

	@Test
	public void testDivide() {
		System.out.println("this is divide()");
	}

}

 运行结果

总结说明

  • @BeforeClass修饰的方法会在所有方法被调用前被执行,而且该方法是静态的,所以当测试类被加载后接着就会运行它,而且在内存中它只会存在一份实例,它比较适合加载配置文件,进行初始化等等I
  • @AfterClass所修饰的方法会在所有方法被调用后被执行,通常用来对资源的清理,如关闭数据库的连接;
  • @Before和@After会在每个测试方法的前后各执行一次。 

 1.5.2JUnit常用注解

  • @Test:将一个普通的方法修饰成为一个测试方法
  •     @Test(expected=XX.class)
  •      * @Test(expected=ArithmeticExceptlon.class):预期被测方法是否抛出ArithmeticException异常 
  •     @Test(timeout=毫秒)
  • @BeforeClass:它会在所有的方法运行前被执行,statlc修饰,只执行一次,
  • @AfterClass:它会在所有的方法运行结束后被执行,static修饰,只执行一次;
  • @Before:会在每一个测试方法被运行前执行一次
  • @After:会在每一个测试方法运行后被执行一次;
  • @lgnore:所修饰的测试方法会被测试运行器忽略
  • @RunWith:可以更改测试运行器org.junit,runner,Runner;

一个JUnit4的单元测试用例执行顺序为:@BeforeClass -> @Before -> @Test -> @After -> @AfterClass;

每一个测试方法的调用顺序为:@Before -> @Test -> @After;
JUnit4和JUnit5对比:

特性Junit4Junit5
在当前类的所有测试方法之前执行。注解在静态方法上。此方法可以包含一些初始化代码。@BeforeClass@BeforeAll
在当前类中的所有测试方法之后执行。注解在静态方法上。此方法可以包含一些清理代码。@Afterclass@AfterAll
在每个测试方法之前执行。注解在非静态方法上。可以重新初始化测试方法所需要使用的类的某些属性。@Before@BeforeEach
在每个测试方法之后执行。注解在非静态方法上。可以回滚测试方法引起的数据库修改。@After@AfterEach

 @Test. @lgnore的测试

public class AnotationTest {

	@Test(expected = ArithmeticException.class)
	public void testDivide() {
		Assert.assertEquals("除法有问题",3,new Calculate().divide(6,0));// 将除数设需为0
	}
    //如果两秒该测试还没有运行结束,那么就测试失败	
	@Test(timeout = 2000)
	public void testwhile() {
		while(true){
			System.out.println("run forever...");//一个死循环
		}
    }
	@Test(timeout = 3000)
	public void testReadFile(){
	try {
		Thread.sleep(2000);//模拟读文件操作
		}
		catch(InterruptedException e){
		e.printStackTrace();
	  }
	}
	@Ignore("....")
	@Test
	public void testIgnore() {
		System.out.println("会运行吗?");
	}
}

 说明:

 1、testDivide()方法中,将除数设为0,本会抛出Error,但设置了@Test(expected=ArithmeticExcepton.class),说明我们预期它会抛出一个算术异常,所以程序结果也符合我们的预期。
2、testWhile()方法是一个死循环,但设置了@Test(timeout=2000),即2秒之后,自动结束循环
3、testReadFile(方法模拟读取文件操作,设置读取超时时间为3秒,等于或大于测试时间则认为不成功,而程序睡眠了2秒,没有超时,这里表示用作一些性能的测试。
4、testlgnore()方法,因为使用的@lgnore注解,所以不会运行。

 1.6 JUnit测试套件使用及参数化设置

@RunWith注解:当类被@RunWith注解修饰,或者类继承了一个被该注解修饰的类,Junit将会使用这个注解所指明的运行器(runner)来运行测试,而不是JUnit默认的运行器

1.6.1JUnit测试套件  

 如果在测试类不增加的情况下,如何运行所有的单元测试代码类?一个个测试类的执行吗?显然繁琐且费劲。

将要运行的测试类集成在我们的测试套件中,比如一个系统功能对应一个测试套件,一个测试套件中包含多个测试类,每次测试系统功能时,只要执行一次测试套件就可以了。
测试类及测试套件代码
新建3个测试任务类:
 

public class TaskTest1 {

	@Test
	public void test() {
		System.out.println("this is TaskTest1...");
	}
}
public class TaskTest2 {

	@Test
	public void test() {
		System.out.println("this is TaskTest2...");
	}

}
public class TaskTest3 {

	@Test
	public void test() {
		System.out.println("this is TaskTest3...");
	}
}

 测试套件类

/**
 * 测试套件:一次性运行TaskTest1、TaskTest2、TaskTest3三个测试类
 */

@RunWith(Suite.class)
@Suite.SuiteClasses({TaskTest1.class,TaskTest2.class,TaskTest3.class})//定义套件运行测试类

public class SuiteTest {
	public class suiteTest {
		/**测试套件就是组织测试类一起运行的
		 * 写一个作为测试套件的入口类,这个类里不包含其他的方法
		 * 更改测试运行器Suite.class
		 * 将要测试的类作为数组传入到suite.suiteclasses()
		*/
	}
}

运行结果

 说明

  • 使用@RunWith注解,修改测试运行器:例如@RunWith(Suite.class),这个类就成为测试套件的入口类
  • @Suite,SuiteClasses()中放入测试套件的测试类,以数组的形式(class1,class2.)作为参数

1.6.2Junit参数化设置 

 如果测试代码大同小异,代码结构都是相同的,不同的只是测试的数据和预期值,那么有没有更好的办法将相同的代码结构提取出来,提髙代码的重用度呢?

解决:进行参数化测试。** 

步骤:
1.要进行参数化测试,需要在类上面指定如下的运行器:@RunWith(Parameterlzed.class);

2.然后,在提供数据的方法上加上一个@Parameters注解,这个方法必须是静态statc的,并且返回一个集合Collection;

3.在测试类的构造方法中为各个参数赋值(构造方法是由JUnit调用的),最后编写测试类,它会根据参数的组数来运行测试多次。

代码

 

//1.更改默认的测试运行器为@Runwith(Parameterized.class)
@RunWith(Parameterized.class)
public class parameterTest {
	
	//2.声明变量存放预期值和测试数据	
	int expected =0;
	int num1 =0;
	int num2 = 0;
	
	//3.声明一个返回值 为Co11ection的公共静态方法,并使用Parameters进行修饰
	@Parameters
	public static Collection<Object[]> data(){
		/**
		 * 4 1 3
		 * 7 3 4 
		 * 9 2 7
		 */
		return Arrays.asList(new Object[][]{{4,1,3},{7,3,4},{9,2,7}});
	}
	
	/**
	 * 构造器方法由JUunit调用
	 * @param expected
	 * @param num1
	 * @param num2
	 */
	//4.为测试类声明一个带有参数的公共构造函数,并在其中为之声明变量赋售
	public parameterTest(int expected,int num1,int num2) {
		this.expected=expected;
		this.num1=num1;
		this.num2=num2;
	}
	//5.测试方法
	@Test
	public void testAdd() {
		Calculate calculate = new Calculate( ); 
		int result = calculate.add(num1,num2);	
		assertEquals("加法运算有问题",expected,result);
	}
}

 

 

 

 


 

  • 32
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值