在完成了數據訪問層的單元之後,接下來看如何編寫服務層(Service)的單元測試。服務層應該是整個系統中得重中之重,嚴密的業務邏輯設計保證了系統穩定運行,所以這一層的單元測試也應該佔很大比重。雖然一般情況下單元測試應該儘量通過mock剝離依賴,但是由於在當前的項目中數據訪問層使用spring-data框架,並沒有包含太多的邏輯,因此我就把服務層和數據訪問層放在做了一個僞單元測試。
一、一般邏輯的單元測試。
這裏採用的方式和數據訪問層幾乎是一樣的,主要包含三步:
1. 通過@DatabaseSetup指定測試用數據集
2. 執行被測試方法
3. 通過Dao從數據庫中查詢數據驗證執行結果
假設要被測試的代碼方法是:
@Service
@Transactional(readOnly = true)
public class ShopServiceImpl extends BaseService implements ShopService{
private Logger logger = LoggerFactory.getLogger(ShopServiceImpl.class);
@Transactional(readOnly = false)
public Floor addFloor(String buildingName, int floorNum, String layout) {
//如果已經存在對應的樓層信息,則拋出已經存在的異常信息
Floor floor = floorDao.findByBuildingNameAndFloorNum(buildingName, floorNum);
if (floor != null) {
throw new OnlineShopException(ExceptionCode.Shop_Floor_Existed);
}
//如果不存在對應的商場信息,則添加新的商場
Building building = buildingDao.findByName(buildingName);
if (building == null) {
building = new Building();
building.setName(buildingName);
buildingDao.save(building);
}
//添加並返回樓層信息
floor = new Floor();
floor.setBuilding(building);
floor.setFloorNum(floorNum);
floor.setMap(layout);
floorDao.save(floor);
return floor;
}
}
其對應的接口是:
public interface ShopService {
public Floor addFloor(String buildingName, int floorNum, String layout);
}
這段邏輯代碼的意思十分簡單和直白,那麼要編寫的單元的測試必須要包含所有分支情況:a. 商場和樓層信息都存在的,拋出異常 b. 商場存在,而樓層不存在, 樓層信息都被添加的。 c. 商場和樓層都不存在,全部新增。這裏就以第一種情況爲例,先準備測試數據:
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<building id="1" name="New House"/>
<floor id="1" building="1" floor_num="2"/>
</dataset>
接着編寫測試用例,注意要必須得註解不能忘掉:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-test.xml")
@Transactional
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
CustomTransactionDbUnitTestExecutionListener.class,
ForeignKeyDisabling.class})
public class ShopServiceTest {
@Autowired
private ShopService shopService;
@Test
@DatabaseSetup("shop/ShopService-addFloorExistException-dataset.xml")
public void testAddFloorExistException(){
try {
shopService.addFloor("New House", 2, "");
fail();
} catch(Exception e){
assertTrue(e instanceof OnlineShopException);
assertEquals(ExceptionCode.Shop_Floor_Existed.code(), ((OnlineShopException)e).getCode());
}
}
}
這個測試和數據訪問層的測試看起來沒有什麼兩樣。
二、使用Mock對象隔離第三方接口
軟件開發中一般都存在和第三方集成的情況,比如調用新浪的認證、百度的地圖等等。那麼在編寫測試的時候,基於效率的考慮,一般情況不會真的去調用這些遠程API(當然應該有其他測試可以及時發現第三方接口的變化),而是假定它們一直會返回預期的結果。這個時候就需要用到mock對象,來模擬這些API產生相應的結果。
在這裏,我是用了mockito,使用十分方便。假如現在用戶登錄時,需要去第三方系統驗證,那麼現在來看如何對這個場景進行測試。還是先來看被測試的方法:
private boolean validateUser(String inputName, String inputPassword) {
return thirdPartyAPI.authenticate(inputName, inputPassword);
}
其中thirdPartyAPI就是第三方用來認證的API。下面來看測試代碼:
public class UserServiceTest {
@Autowired
private UserService userService;
private ThirdPartyAPI mockThirdPartyAPI = mock(ThirdPartyAPI.class);
@Test
public void testLogin(){
//指定mock對象特定操作的返回結果
when(mockThirdPartyAPI.authenticate("jiml", "jiml")).thenReturn(true);
//通過Setter用mock對象替換由Spring初始化的第三方依賴
((UserServiceImpl)userService).setThirdPartyAPI(mockThirdPartyAPI);
boolean loginStatus = userService.login("jiml", "jiml");
assertTrue(loginStatus);
}
}
其實服務層的測試並沒有太多的新東西,而最關鍵的問題是如何把邏輯中各個分支都能測試到,使測試真正起到爲軟件質量保駕護航的作用。