问题记录本1–单元测试PowerMockito @Before与@BeforeClass
最近写单元测试的时候遇到一个问题,记录如下:
被测试类:
public class Car() {
private final static LogClass LOGGER = LogFactory.getLog(Car.class);
public boolean drive() {
// 打开车门
String doorResult = Door.getInstance().open();
try {
if (doorResult != null && doorResult.isNotEmpty) {
// 发动汽车
return startCar();
}
} catch (Exception ex) {
LOGGER.error("汽车发动异常", ex);
}
return false;
}
public boolean startCar() {
...
}
}
Car是现在需要被测试的类,其中该类中有一个静态变量LOGGER,drive方法中构建了一个单例Door,那么如果要写单元测试,需要模拟startCar抛出异常的case,那就要将静态变量和单例mock出来。一开始我的单测是这么写的:
@RunWith(PowerMockRunner.class)
@PrepareForTest({
Car.class,
Door.class
})
public class CarTest {
@InjectMocks
private Car car;
private Door door;
private LogClass logger;
@Before
public void setUp() {
PowerMockito.mockStatic(Door.class);
PowerMockito.mockStatic(LogFactory.class);
door = PowerMockito.mock(Door.class);
PowerMockito.when(Door.getInstance()).thenReturn(door);
logger = PowerMockito.mock(LogClass.class);
PowerMockito.when(LogFactory.getLog(Car.class)).thenReturn(logger);
}
@Test
public void driveTest1() {
PowerMockito.when(door.open).thenReturn("Success");
Exception ex = new JSONException("a exception");
PowerMockito.when(car.startCar()).thenThrow(ex);
boolean result = car.drive();
Mockito.verify(logger).error("汽车发动异常", ex);
}
@Test
public void driveTest2() {
PowerMockito.when(door.open).thenReturn("Success");
PowerMockito.when(car.startCar()).thenReturn(true);
boolean result = car.drive();
Assert.assertTrue(result);
}
}
我的本意是想要把日志工厂LogFactory给mock出来,然后再进行异常抛出的时候就能够验证这个异常。
但是在实际执行这个单测时,logger并不是我Mock出来的那一个,而是在被测类中构建的。
这是因为被测类中的LOGGER 是一个静态变量,会在类加载的时候就被构建出来,接着代码才会走到单测setUp方法中,此时再去构建一个mock的logger是没有用的。
于是将单测类中的 @Before 改成了 @BeforeClass,这是为了在类加载之前先执行我的mock步骤,改后的代码如下:
@RunWith(PowerMockRunner.class)
@PrepareForTest({
Car.class,
Door.class
})
public class CarTest {
@InjectMocks
private Car car;
private static Door door;
private static LogClass logger;
@BeforeClass
public static void setUp() {
PowerMockito.mockStatic(Door.class);
PowerMockito.mockStatic(LogFactory.class);
door = PowerMockito.mock(Door.class);
PowerMockito.when(Door.getInstance()).thenReturn(door);
logger = PowerMockito.mock(LogClass.class);
PowerMockito.when(LogFactory.getLog(Car.class)).thenReturn(logger);
}
@Test
public void driveTest1() {
PowerMockito.when(door.open).thenReturn("Success");
Exception ex = new JSONException("a exception");
PowerMockito.when(car.startCar()).thenThrow(ex);
boolean result = car.drive();
Mockito.verify(logger).error("汽车发动异常", ex);
}
@Test
public void driveTest2() {
PowerMockito.when(door.open).thenReturn("Success");
PowerMockito.when(car.startCar()).thenReturn(true);
boolean result = car.drive();
Assert.assertTrue(result);
}
}
此时解决了上述logger无法mock的问题,然而新的问题又出来了,因为我写了一个driveTest2方法,在单独执行的时候,Door确实是我mock出来的一个对象,但是在执行整个Test类的时候,发现driveTest2中的Door又是在代码中构建出来的对象了(之所以会发现,是因为Door在构建的时候会有一些日志打印在控制台中,如果是mock出来的就完全不会)。
为什么在单测修改之前不会出现这个问题呢?
为什么在单独执行每个单测方法的时候不会出现这个问题呢?
于是我想到了会不会是改成了@BeforeClass导致的,查阅了一些资料,发现果真如此
@BeforeClass在类中只会执行一次,而@Before会在每个方法执行之前都执行一次
接着我将代码修改如下,让 @BeforeClass 和 @Before 各司其职,完美解决问题。
@RunWith(PowerMockRunner.class)
@PrepareForTest({
Car.class,
Door.class
})
public class CarTest {
@InjectMocks
private Car car;
private Door door;
private static LogClass logger;
@BeforeClass
public static void setUp() {
PowerMockito.mockStatic(LogFactory.class);
logger = PowerMockito.mock(LogClass.class);
PowerMockito.when(LogFactory.getLog(Car.class)).thenReturn(logger);
}
@Before
public void setUp2() {
PowerMockito.mockStatic(Door.class);
door = PowerMockito.mock(Door.class);
PowerMockito.when(Door.getInstance()).thenReturn(door);
}
@Test
public void driveTest1() {
PowerMockito.when(door.open).thenReturn("Success");
Exception ex = new JSONException("a exception");
PowerMockito.when(car.startCar()).thenThrow(ex);
boolean result = car.drive();
Mockito.verify(logger).error("汽车发动异常", ex);
}
@Test
public void driveTest2() {
PowerMockito.when(door.open).thenReturn("Success");
PowerMockito.when(car.startCar()).thenReturn(true);
boolean result = car.drive();
Assert.assertTrue(result);
}
}