一、JUnit常用注解与断言
1.@Test
@Test注解的public void方法将会被当做测试用例
JUnit每次都会创建一个新的测试实例,然后调用@Test注解方法,任何异常的抛出都会认为测试失败
@Test注解提供2个参数:
I.”expected”,定义测试方法应该抛出的异常,如果测试方法没有抛出异常或者抛出了一个不同的异常,测试失败
@Test(expected = IllegalArgumentException.class) 表示验证这个测试方法将抛出 IllegalArgumentException 异常,如果没有抛出的话,则测试失败。
II.”timeout”,如果测试运行时间长于该定义时间,测试失败(单位为毫秒)
2.@Before
当编写测试方法时,经常会发现一些方法在执行前需要创建相同的对象
使用@Before注解一个public void 方法会使该方法在所有@Test注解方法被执行前执行(那么就可以在该方法中创建相同的对象),父类的@Before注解方法会在子类的@Before注解方法执行前执行
3.@After
如果在@Before注解方法中分配了额外的资源,那么在测试执行完后,需要释放分配的资源。
使用@After注解一个public void方法会使该方法在所有@Test注解方法执行后被执行(即使在@Before注解方法、@Test注解方法中抛出了异常,所有的@After注解方法依然会被执行),父类中的@After注解方法会在子类@After注解方法执行后被执行
4.@BeforeClass 和 @AfterClass
@BeforeClass 和 @AfterClass。 @BeforeClass 的作用是,在跑一个测试类的所有测试方法之前,会执行一次被 @BeforeClass 修饰的方法,执行完所有测试方法之后,会执行一遍被 @AfterClass 修饰的方法。这两个方法可以用来setup和release一些公共的资源,需要注意的是,被这两个annotation修饰的方法必须是静态的,并且只执行一次。
5.Assert
Assert(断言)在单元测试中十分常用,包括有
Assert.assertTrue();
Assert.assertEquals();
Assert.assertNotNull();
注:
1.Double类型的数据比较时需要用以下语句,其中delta为允许误差
Assert.assertEquals(expected, actual, delta);
2.整体断言:当需要对大量字段进来断言时,可以采用”整体断言”的方法。
3.在实际编写测试代码时,可以先不写断言,而是把结果转为JSON并打印出来,经过人工检验无误后,再将打印结果作为预期结果加上断言,这可以大大节省时间。
二、Mock
1.Mock
在维基百科上这样描述Mock:In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. A computer programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behavior of a human in vehicle impacts.
Mock通常是指,在测试一个对象A时,我们构造一些假的对象来模拟与A之间的交互,而这些Mock对象的行为是我们事先设定且符合预期。通过这些Mock对象来测试A在正常逻辑,异常逻辑或压力情况下工作是否正常。
引入Mock最大的优势在于:Mock的行为固定,它确保当你访问该Mock的某个方法时总是能够获得一个没有任何逻辑的直接就返回的预期结果。
Mock Object的使用通常会带来以下一些好处:
- 隔绝其他模块出错引起本模块的测试错误。
- 隔绝其他模块的开发状态,只要定义好接口,不用管他们开发有没有完成。
- 一些速度较慢的操作,可以用Mock Object代替,快速返回。
对于分布式系统的测试,使用Mock Object会有另外两项很重要的收益:
- 通过Mock Object可以将一些分布式测试转化为本地的测试
将Mock用于压力测试,可以解决测试集群无法模拟线上集群大规模下的压力
2.Mock的应用场景
在使用Mock的过程中,发现Mock是有一些通用性的,对于一些应用场景,是非常适合使用Mock的:
真实对象具有不可确定的行为(产生不可预测的结果,如股票的行情)
- 真实对象很难被创建(比如具体的web容器)
- 真实对象的某些行为很难触发(比如网络错误)
- 真实情况令程序的运行速度很慢
- 真实对象有用户界面
- 测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了)
- 真实对象实际上并不存在(当需要和其他开发小组,或者新的硬件系统打交道的时候,这是一个普遍的问题)
当然,也有一些不得不Mock的场景:
- 一些比较难构造的Object:这类Object通常有很多依赖,在单元测试中构造出这样类通常花费的成本太大。
- 执行操作的时间较长Object:有一些Object的操作费时,而被测对象依赖于这一个操作的执行结果,例如大文件写操作,数据的更新等等,出于测试的需求,通常将这类操作进行Mock。
- 异常逻辑:一些异常的逻辑往往在正常测试中是很难触发的,通过Mock可以人为的控制触发异常逻辑。
在一些压力测试的场景下,也不得不使用Mock,例如在分布式系统测试中,通常需要测试一些单点(如namenode,jobtracker)在压力场景下的工作是否正常。而通常测试集群在正常逻辑下无法提供足够的压力(主要原因是受限于机器数量),这时候就需要应用Mock去满足。
三、PowerMock+Mockito+JUnit
在使用PowerMock+Mockito+JUnit来进行单元测试的过程中经常需要mock掉一些对象,比如我们要测试的某个ServiceA类的方法method()调用了另外一个外部ServiceB类的方法,这里我们只是为了测试方法method()的逻辑,并不想真正执行ServiceB的方法,所以我们需要mock掉ServiceB的方法调用,这时我们可以在测试类首部使用@Mock和@InjectMocks注解进行依赖类和被测类的初始化和注入。这两个注解的区别和用法如下:
@Mock:创建一个Mock对象; @InjectMocks:创建一个实例,其余用@Mock(或@Spy)注解创建的Mock对象将被注入到用该实例中;
1.@Mock和@InjectMocks的用法
以下列代码为例
public class ServiceA {
private ServiceB serviceB = new ServiceB();
public int method () {
return serviceB.check();
}
}
public class ServiceB {
public int check() {
return 0;
}
}
1)使用这种注解的方式来初始化外部依赖类和被测类,PowerMock框架会自动将外部依赖类注入到被测类中,并且这些实例可以在该测试类的所有测试方法中复用;我们也可以在测试方法中需要时new一个被测类实例,mock一个外部依赖类对象,如果在被测类中有外部依赖类这个属性的setter()方法,则可以调用setter()方法将这个外部依赖类对象注入到被测类实例中,如果没有setter()方法,则可通过WhiteBox.setInternal()方法将依赖类对象注入被测类实例中;
@RunWith(PowerMockRunner.class)
public class ServiceATest {
@Test
public void testMethod () {
ServiceA serviceA = new ServiceA();
ServiceB serviceB = PowerMockito.mock(ServiceB.class);
WhiteBox.setInternal(serviceA, "serviceB", serviceB);
// ...
}
}
2)如果是使用了@Mock、@InjectMocks这种注解的方式来初始化实例,则需要配合使用MockitoAnnotations.initMocks(this)来真正完成测试类中这些field(外部依赖类和被测类这里都是测试类的field)的初始化,通常我们配合JUnit的@Before注解来实现
public class ServiceATest {
@InjectMocks
private ServiceA serviceA;
@Mock
private ServiceB serviceB;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void test() {
serviceA.method();
}
}
2.private static字段
在测试方法中我们可以通过Whitebox.getInternalState()获取被测类的private static字段,如下:
被测ServiceA类中有一个名为URL的private的常量字段
public class ServiceA {
ServiceB serviceB = new ServiceB();
private static final String URL = "https://www.google.com";
public int method () {
return serviceB.getIntNumber();
}
}
@InjectMocks
ServiceA serviceA;
@Test
public void testURL () {
String URL = Whitebox.getInternalState(ServiceA.class, "URL");
Assert.assertEquals(URL, "https://www.google.com);
// ...
}
3.private方法
由于private的方法只提供了本类的可访问性,所以对于传统的单元测试来说private方法就意味着不可测性,好在java拥有功能强大的反射机制,利用反射机制我们可以获取到被测类的private方法,在PowerMock框架中,框架本身也为我们提供了获取被测类private方法的方法,下面总结一下在单元测试中我们可以通过哪些方式获取被测类的private方法。
public class PrivateMethod {
// 私有方法
private int add(int a, int b){
return a + b;
}
}
1)直接使用java反射机制
import java.lang.reflect.Method;
import org.junit.Assert;
import org.junit.Test;
public class PrivateMethodTest {
@Test
public void testAdd() throws Exception {
// 获取目标类的class对象
Class<PrivateMethod> class1 = PrivateMethod.class;
// 获取目标类的实例
Object instance = class1.newInstance();
// getDeclaredMethod() 可获取公共、保护、默认(包)访问和私有方法,但不包括继承的方法;
// getMethod() 只可获取公共的方法
Method addMethod = class1.getDeclaredMethod("add", new Class[]{int.class, int.class});
// 值为true时反射的对象在使用时让一切已有的访问权限取消
addMethod.setAccessible(true);
int result = (int) addMethod.invoke(instance, new Object[]{1, 2});
// 使用Assert的断言方法
Assert.assertEquals(3, result);
}
}
2)使用PowerMockito.method()方法
import java.lang.reflect.Method;
import org.junit.Assert;
import org.junit.Test;
import org.powermock.api.mockito.PowerMockito;
public class PrivateMethodTest {
@Test
public void testAdd () throws Exception {
PrivateMethod privateMethod = new PrivateMethod();
// 获取Method对象,
Method addMethod = PowerMockito.method(privateMethod.getClass(), "add", new Class[]{int.class, int.class});
addMethod.setAccessible(true);
// 调用Method的invoke方法来执行
int result = (int) addMethod.invoke(privateMethod, new Object[]{1, 2});
Assert.assertEquals(3, result);
}
}
3)使用Whitebox.invokeMethod()方法
import org.junit.Assert;
import org.junit.Test;
import org.powermock.reflect.Whitebox;
public class PrivateMethodTest {
@Test
public void testAdd () throws Exception {
PrivateMethod privateMethod = new PrivateMethod();
// 直接调用Whitebox.invokeMethod()方法来执行被测类的private方法
int result = Whitebox.invokeMethod(privateMethod, "add", new Object[]{1, 2});
Assert.assertEquals(3, result);
}
}