天天看點

問題記錄本1--單元測試PowerMockito @Before與@BeforeClass問題記錄本1–單元測試PowerMockito @Before與@BeforeClass

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

繼續閱讀