問題記錄本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);
}
}