天天看點

編寫真正的單元測試

上一篇中介紹了,何為測試驅動,為什麼需要測試驅動? 現在我們來看看怎樣編寫單元測試。

為了更真實的展現單元測試的魅力,我使用目前工作中的項目一段代碼(因為我太懶得在寫一段代碼。。。。),你不需要了解具體的業務,隻需要了解如何如何寫單元測試,以及感受單元測試的魅力就可以了。

這裡我們改變一種方式,即我們先寫功能代碼,再補充單元測試,很多團隊都是這樣使用,雖然這樣并不好,可是很多時候,我們新加入一個團隊,不可能負責去做一個新的項目,都是在維護老的項目,并且當時的團隊為了更快的編碼而沒有寫單元測試(這樣并不能提高效率,但是人們為了短期的利益,而這樣的事情在我們生活中處處可見。)

##功能代碼示例

簡單了解,隻需要注意 條件判斷和外部依賴(調用其他類的方法),明白我們的單元測試代碼需要覆寫到所有的條件判斷,和隔離MOCK 外部依賴。

/**
     * 根據是否随機設定查詢參數
     * @param params
     * @param projectType
     * @param prjId
     * @return
     */
    private  Map<String, Object> setRandParam(Map<String, Object> params,Integer projectType,Integer prjId){
    	 
    	 int allocateedAssetCountMin = (Integer)params.get("allocateedAssetCountMin");//資産池清單(前背景)最小項目已比對資産條數
         int assetRandRoundCount = (Integer)params.get("assetRandRoundCount");//資産池(前背景)随機資産-每次查詢多少條
    	 
         
         if (projectType != null && projectType.equals(ProjectType.LHB.getValue())) {
             prjId = LHB_PROJECTID;
         }

         boolean isRand = false;//是否使用随機債權,預設不使用

         //1.根據prjId确認是否需要使用随機債權
         if (prjId != null) {
             params.put("projectId", prjId);

            
             int investShareCount = investRepository.queryInvtShareCountByPrjId(prjId.toString());
             int allocatedAssetCount = assetRepository.queryAllocatedAssetCountByPrjId(prjId.toString());

             
             if (investShareCount == 0 || allocatedAssetCount < allocateedAssetCountMin) {
                 int randAssetListCount = assetRepository.queryAssetPoolRandListCount(params);//随機債權資料總條數
                 isRand = true;
                 Integer begin = getPoolRandListBegin(randAssetListCount, prjId, PRIME_NUMBER, assetRandRoundCount);
                 Integer end = begin + assetRandRoundCount;
                 params.put("begin", begin);
                 params.put("end", end);
             }
         }
    	
         MapUtil.addValueToMap(params, "isRand", isRand);
    	
    	return params;
    	
    }
           

這段代碼中,有條件判斷,和依賴外部(資料庫)。我們需要對這個做單元測試,要保證不受外界影響(資料庫的結果)是以我們需要 MOCK(模拟外部調用傳回的資料)

首先為了能夠 運作單元測試我們需要引入 junit(我使用過的是Java,其他語言有其他的測試架構)

因為我們需要 @test 注解,和強大的斷言 Assert .

<!-- https://mvnrepository.com/artifact/junit/junit -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
		<scope>test</scope>
	</dependency>
		
           

為了mock 外部的依賴,我們需要使用mock 工具,這裡使用 powerMock, 引入相關的 依賴包(maven搜尋,這裡不再展示)。

為了能夠MOCK ,我們在測試類上面使用注解 @RunWith(PowerMockRunner.class) 代表這個類需要使用mock。

在類中對 測試的類使用 @injectMocks 注解我們測試的類, @mock 注解我們外部依賴的類。

@InjectMocks 
	private AssetServiceFacade assetServiceFacade;
	@Mock
	private InvestRepository investRepository;
	
           

測試類的命名要求 類名+Test ,測試的方法 test+方法名,編寫的測試用例上面 注解@Test ,junit就知道了這個方法是測試用例(畢竟測試類中還存在一些資料準備的方法)。

現在來看看,測試用代碼:

根據功能代碼中的條件判斷,我們需要寫三個用例 (這裡被測試代碼由于是私有的,編寫比較特殊需要使用反射來擷取方法,公共方法,直接調用類就可以了。)

##用例一:條件為NULL

@Test
	public void  testSetRandParamWithNull() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{

		//準備資料
		Map<String, Object> params = Maps.newHashMap();
		Integer prjId = null;
		Integer projectType = null;
		MapUtil.addValueToMap(params, "allocateedAssetCountMin", 500);
		MapUtil.addValueToMap(params, "assetRandRoundCount", 3000);
		
		//使用反射,調用方法
		Class assetFacade = assetServiceFacade.getClass();
		Method setRandParam = assetFacade.getDeclaredMethod("setRandParam",Map.class,Integer.class,Integer.class);
		setRandParam.setAccessible(true);
		
		params = (Map<String, Object>) setRandParam.invoke(assetServiceFacade, params,projectType,prjId);
		
		Assert.assertEquals(false, params.get("isRand"));
		
	}
           

用例二

@Test
	public void testSetRandParamWithLHB() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{

		//準備資料
		Map<String, Object> params = Maps.newHashMap();
		Integer prjId = 3;
		Integer projectType = 104040; //靈活寶
		MapUtil.addValueToMap(params, "allocateedAssetCountMin", 500);
		MapUtil.addValueToMap(params, "assetRandRoundCount", 3000);
		
		//使用反射,調用方法
		Class assetFacade = assetServiceFacade.getClass();
		Method  setRandParam = assetFacade.getDeclaredMethod("setRandParam", Map.class,Integer.class,Integer.class);
		setRandParam.setAccessible(true);
		
		//mock
		when(investRepository.queryInvtShareCountByPrjId(anyString())).thenReturn((Integer)20);
		when(assetRepository.queryAllocatedAssetCountByPrjId(anyString())).thenReturn(600);
		params = (Map<String, Object>) setRandParam.invoke(assetServiceFacade, params,projectType,prjId);
		
		Assert.assertEquals(1, params.get("projectId"));
		Assert.assertEquals(false, params.get("isRand"));
		
	}
           

用例三

@Test
	public void testSetRandParamWithRand() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
		
		//準備資料
		Map<String, Object> params = Maps.newHashMap();
		Integer prjId = 3;
		Integer projectType = 104040;
		MapUtil.addValueToMap(params, "allocateedAssetCountMin", 500);
		MapUtil.addValueToMap(params,"assetRandRoundCount", 3000);
		
		//使用反射,調用方法
		Class assetFacade = assetServiceFacade.getClass();
		Method  setRandParam = assetFacade.getDeclaredMethod("setRandParam", Map.class,Integer.class,Integer.class);
		setRandParam.setAccessible(true);
		
		when(investRepository.queryInvtShareCountByPrjId(anyString())).thenReturn(20);
		when(assetRepository.queryAllocatedAssetCountByPrjId(anyString())).thenReturn(20);
		
		params = (Map<String, Object>) setRandParam.invoke(assetServiceFacade, params,projectType,prjId);
		
		Assert.assertEquals(true, params.get("isRand"));
		
	}
           

測試步驟

可以看到,上面三個的用例的大緻步驟都是:

1.準備資料 (方法的參數)

2.mock相關的外部依賴,直接給出我們結果,when(調用方法).thenReturn(結果)。 當調用這個方法,直接傳回 期望的結果。

3.斷言判斷關鍵結果是否符合我們的期望。(如果不符合,則修改功能代碼,前提保證測試的邏輯是沒有問題的。)

運作測試

這個時候在單元測試右鍵 Run as -> Junit Test ,可以看到結果,我的這個,我已經運作成功,但是不認為這是一次就運作成功。事情都是曲折發展,在你編寫單元測試的過程中(我們這種方式先編碼,再補單元測試)會經常 修改單元測試代碼保證單元測試代碼沒有問題,如果之後測試還跑不通,那麼這個時候大膽的修改代碼,因為你有單元測試這個強大的工具保駕護航。

編寫真正的單元測試

測試覆寫率

測試完成之後,你還需要保證,你的單元測試代碼的沒有忘記哪個分支條件,此時使用 檢測覆寫率工具 EclEmma

進行覆寫的檢查。

安裝完 EclEmma ,插件右鍵 coverage as —> junit test ,可以看到我們的功能代碼,有了顔色标記,綠色代表 測試代碼覆寫到了,紅色代表沒有覆寫到,黃色代表沒有完全覆寫。

編寫真正的單元測試

##總結

那麼現在我們總結一下,如果編寫單元測試代碼:

  1. 準備 junit 測試架構
  2. 建立測試類
  3. 引入 mock工具,mock外部依賴,我們這裡使用 powerMock,你可以選擇其他的。
  4. 檢測單元測試覆寫率

繼續閱讀