精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

如何寫好單元測試?

開發(fā) 開發(fā)工具
單元測試的好處到底有哪些?每次單測啟動應用,太耗時,怎么辦?二方三方接口可能存在日常沒法用,只能上預發(fā)/正式的情況,上預發(fā)測低效如何處理?本文分享三個單元測試神器及相關經(jīng)驗總結。

[[344639]]

阿里妹導讀:單元測試的好處到底有哪些?每次單測啟動應用,太耗時,怎么辦?二方三方接口可能存在日常沒法用,只能上預發(fā)/正式的情況,上預發(fā)測低效如何處理?本文分享三個單元測試神器及相關經(jīng)驗總結。

一 首先什么是好代碼?

 

Q1:好代碼應具備可讀性,可測試性,可擴展性等等,那么如何寫出好代碼?

A:設計思想 & 編碼規(guī)范。

二 設計思想&設計原則&設計模式

1 設計原則(S.O.L.I.D)

SRP 單一職責原則

  • 軟件模塊應該只有一個被修改的理由。在大多數(shù)情況下,編寫Java代碼時都會將單一職責原則應用于類。單一職責原則可被視為使封裝工作達到最佳狀態(tài)的良好實踐。更改的理由是:需要修改代碼。
  • 單一原則,類、方法只干一件事。

OCP 開閉原則

  • 模塊、類和函數(shù)應該對擴展開放,對修改關閉。
  • 通過繼承和多態(tài)擴展來添加新功能。開閉原則是最重要的設計原則之一,是大多數(shù)設計模式的基礎。
  • 軟件建設一個復雜的結構,當我們完成其中的一部分,就應該不要修改它,而是在其基礎上繼續(xù)建設。

LSP 里式替換原則

  • 在設計模塊和類時,必須確保派生類型從行為的角度來看是可替代的。
  • 使用父類的地方都可以用子類替代。
    • 父類最好為抽象類。
    • 子類可實現(xiàn)父類的非抽象方法,盡量不要覆蓋重寫已實現(xiàn)的方法。
    • 子類可寫自身的方法,有自身的特性,在父類的基礎上擴建。
    • 子類覆蓋重寫父類方法時,方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松,后置條件(返回值)要更嚴格。

ISP 接口隔離原則

  • 減少了代碼耦合,使軟件更健壯,更易于維護和擴展。
  • 客戶端不應該依賴它所不需要的接口。

DIP 依賴倒置原則

  • 高級模塊不應該依賴低級模塊,兩者都應該依賴抽象。
  • 抽象不應該依賴于細節(jié),細節(jié)應該依賴于抽象。

DRY 原則、KISS 原則、YAGNI 原則、LOD 法則

  • DRY:不要干重復的事兒。
  • KISS:不要干復雜的事兒,思從深而行從簡。
  • YAGNI:不要干不需要的事兒,尺度把握尤為重要,超越尺度則會有過度設計之嫌。
  • LOD:最小依賴。

設計模式

設計模式最重要的點還是在于解耦和復用,創(chuàng)建型模式將創(chuàng)建代碼與使用代碼解耦,結構型模式是將功能代碼解耦,行為型模式將行為代碼解耦,最終達到高內聚,松耦合的目標,設計模式體現(xiàn)了設計原則。

附:我們經(jīng)常說的“高內聚 松耦合”究竟什么是高內聚,什么是松耦合?

  • 高內聚:相近功能放在同一類中,相近功能往往會被同時修改,放到同一個類中在修改時,代碼更易維護(指導類本身的設計)
  • 松耦合:類與類之間的依賴關系簡單清晰,一個類的代碼改動不會或者很少導致依賴類的代碼修改(指導類間依賴關系設計)

Q2: 那么如何驗證代碼是好代碼呢?

A: CR & 單測(下面進入正題^_^)

三 什么是單測?

單元測試(unit testing),指由開發(fā)人員對軟件中的最小可測試單元進行檢查和驗證。對于單元測試中單元的含義,一般來說,要根據(jù)實際情況去判定其具體含義,如C語言中單元指一個函數(shù),Java里單元指一個類,圖形化的軟件中可以指一個窗口或一個菜單等。總的來說,單元就是人為規(guī)定的最小的被測功能模塊。單元測試是在軟件開發(fā)過程中要進行的最低級別的測試活動,軟件的獨立單元將在與程序的其他部分相隔離的情況下進行測試。

來源:https://baike.baidu.com/item/單元測試

四 為什么要寫單測?

1 異(che)常(huo)場(xian)景(chang)

相信大家肯定遇到過以下幾種情況:

  • 測試環(huán)境沒問題,線上怎么就不行。
  • 所有異常捕獲,一切盡在掌控(你以為你以為的是你以為的)。
  • 祖?zhèn)鞔a,改個小功能(只有上帝知道)。
  • .....

要想故障出的少,還得單測好好搞。

2 優(yōu)點

提高代碼正確性

  • 流程判讀符合預期,按照步驟運行,邏輯正確。
  • 執(zhí)行結果符合預期,代碼執(zhí)行后,結果正確。
  • 異常輸出符合預期,執(zhí)行異常或者錯誤,超越程序邊界,保護自身。
  • 代碼質量符合預期,效率,響應時間,資源消耗等。

發(fā)現(xiàn)設計問題

  • 代碼可測性差
  • 方法封裝不合理
  • 流程不合理
  • 設計漏洞等

提升代碼可讀性

  • 易寫單測的方法一定是簡單好理解的,可讀性是高的,反之難寫的單測代碼是復雜的,可讀性差的。

順便微重構

  • 如設計不合理可微重構,保證代碼的可讀性以及健壯性。

提升開發(fā)人員自信心

  • 經(jīng)過單元測試,能讓程序員對自己的代碼質量更有信心,對實現(xiàn)方式記憶更深。

啟動速度,提升效率

不用重復啟動Pandora容器,浪費大量時間在容器啟動上,方便邏輯驗證。

場景保存(多場景)

  • 在HSF控制臺中只能保存一套參數(shù),而單測可保存多套參數(shù),覆蓋各個場景,多條分支,就是一個個測試用例。

CodeReview時作為重點CR的地方

好的單測可作為指導文檔,方便使用者使用及閱讀

  • 寫起來,相信你會發(fā)現(xiàn)更多單測帶來的價值。

3 舉個小例子

改動前:OSS文件夾概念是通過文件名創(chuàng)建的,下面改動前的方法入?yún)⑹荈ile,該方法可以正常使用,但是在寫單測的時候,我發(fā)現(xiàn)使用文件有兩個成本:

  • 必須要有默認文件。
  • 要編寫獲取文件的路徑的方法。

坑:本地獲取的路徑與在容器獲取的路徑是不一致的,復雜度明顯增高。

  1. /** 
  2.  * 向阿里云的OSS存儲中存儲文件 (改動前) 
  3.  * 
  4.  * @param client OSS客戶端 
  5.  * @param file   上傳文件 
  6.  * @return String 唯一MD5數(shù)字簽名 
  7.  */ 
  8.  private static void uploadObject2Oss(OSS client, File file, String bucketName, String dirName) throws Exception { 
  9.      InputStream is = new FileInputStream(file); 
  10.      String fileName = file.getName(); 
  11.      Long fileSize = file.length(); 
  12.      //創(chuàng)建上傳Object的Metadata 
  13.      ObjectMetadata metadata = new ObjectMetadata(); 
  14.      metadata.setContentLength(is.available()); 
  15.      metadata.setCacheControl("no-cache"); 
  16.      metadata.setHeader("Pragma""no-cache"); 
  17.      metadata.setContentEncoding("utf-8"); 
  18.      metadata.setContentType(getContentType(fileName)); 
  19.      metadata.setContentDisposition("filename/filesize=" + fileName + "/" + fileSize + "Byte."); 
  20.      //上傳文件 
  21.      client.putObject(bucketName, dirName + PublicConstant.DIAGONAL_CHARACTER + fileName, is, metadata); 

改動后:將入?yún)ile修改為inputStream,這樣便可省去創(chuàng)建文件以及編寫獲取獲取文件路徑方法,同時還避免了獲取路徑的坑,一舉兩得,也通過單測找到了代碼設計不合理之處。

  1. /** 
  2.  * 向阿里云的OSS存儲中存儲文件(改動后) 
  3.  * 
  4.  * @param client   OSS 上傳client 
  5.  * @param bucketName bucketName 
  6.  * @param dirName  目錄 
  7.  * @param is       輸入流 
  8.  * @param fileName 文件名 
  9.  * @param fileSize 文件大小 
  10.  * @throws Exception 
  11.  */ 
  12.  private void uploadObject2Oss(OSS client, String bucketName, String dirName, InputStream is, String fileName, 
  13.     long fileSize) throws Exception { 
  14.     //創(chuàng)建上傳Object的Metadata 
  15.     ObjectMetadata metadata = new ObjectMetadata(); 
  16.     metadata.setContentLength(is.available()); 
  17.     metadata.setCacheControl("no-cache"); 
  18.     metadata.setHeader("Pragma""no-cache"); 
  19.     metadata.setContentEncoding("utf-8"); 
  20.     metadata.setContentType(getContentType(fileName)); 
  21.     metadata.setContentDisposition("filename/filesize=" + fileName + "/" + fileSize + "Byte."); 
  22.     //上傳文件 
  23.     client.putObject(bucketName, dirName + PublicConstant.DIAGONAL_CHARACTER + fileName, is, metadata); 

4 還想再舉一個

以下這個方法先不說可讀性問題,單從編寫單測來驗證邏輯是否正確,在寫單測時需要:

  • 構造sourceInfos列表
  • 構造String數(shù)組
  • 構造map對象
  • 構造List
  • 構造User 對象

顯然這個方法是非常復雜的,但是邏輯就是得到一個指定長度列表。

  1. /** 
  2.  * 按比例混排結果 (改動前) 
  3.  * @param sourceInfos 渠道配比信息 
  4.  * @param resultMap 結果 
  5.  * @param pageSize 總條數(shù) 
  6.  * @param aliuid 用戶id 
  7.  * @return 結果集 
  8.  */ 
  9. private List<String> getResultList(List<String[]> sourceInfos, Map<String, List<String>> resultMap, int pageSize, User user) { 
  10.     Map<String, Integer> sourceNumMap = new HashMap<>(sourceInfos.size()); 
  11.     sourceInfos.stream().forEach(s -> sourceNumMap.put(s[0], Integer.parseInt(s[1]) * pageSize / 100)); 
  12.     List<String> resultList = new ArrayList<>(); 
  13.     resultMap.forEach((s, strings) -> resultList.addAll(strings.stream().limit(sourceNumMap.get(s)).collect( 
  14.         Collectors.toList()))); 
  15.     // 彌補條數(shù),防止數(shù)據(jù)量不足 
  16.     if (resultList.size() < pageSize) { 
  17.         compensate(resultList, pageSize, user.getAliuid()); 
  18.     } 
  19.     return resultList; 

改動后:將入?yún)⒏臑長ist sourceInfos, int pageSize, String aliuid,將String[]改為SourceInfo,提升代碼可讀性,否則無從得知s[0]表示什么,s[1]表示什么,在寫單測時需要:

  • 構造List列表
  • 構造SourceInfo對象

經(jīng)過改造,可測試性、可讀性均有提升,另外在這個例子中其實user對象只使用了aliuid,無需傳入整個對象,遵循KISS原則。

  1. /** 
  2.  * 按比例混排結果 
  3.  * @param sourceInfos 渠道配比信息 
  4.  * @param pageSize 條數(shù) 
  5.  * @param aliuid 用戶id 
  6.  * @return 結果集 
  7.  */ 
  8. private List<String> getResultList(List<SourceInfo> sourceInfos, int pageSize, String aliuid) { 
  9.     // 獲取結果集 
  10.     List<String> resultList = sourceInfos.stream() 
  11.         .flatMap(sourceInfo -> { 
  12.             int needNum = (int)(sourceInfo.getSourceRatio() * pageSize / 100); 
  13.             return listSource(sourceInfo.getSourceChannel(), needNum, aliuid).stream(); 
  14.         }).collect(Collectors.toList()); 
  15.     // 補償數(shù)據(jù) 
  16.     compensate(resultList, pageSize, aliuid()); 
  17.     return resultList; 

五 如何寫好單測?

1 工具

工欲善其事必先利其器,抗拒寫單測的其中最主要的一個原因就是沒有神器在手!

Fast-tester

每次啟動應用動輒就是幾分鐘起,想要測試一個方法,上個廁所回來可能應用還沒啟動,如此低效,怎么愿意去寫,fast_tester只需要啟動應用一次(tip: 添加注解及測試方法需要重新啟動應用),支持測試代碼熱更新,后續(xù)可隨意編寫測試方法,一個字“秀”!

使用方式:

(1)需要引入jar包

  1. <dependency> 
  2.     <groupId>com.alibaba</groupId> 
  3.     <artifactId>fast-tester</artifactId> 
  4.     <version>1.3</version> 
  5.     <scope>test</scope> 
  6. </dependency> 

(2)在test的package下創(chuàng)建TestApplication

  1. /** 
  2.  * @author QZJ 
  3.  * @date 2020-08-03 
  4.  */ 
  5. @SpringBootApplication 
  6. public class TestApplication { 
  7.     public static void main(String[] args){ 
  8.         PandoraBootstrap.run(args); 
  9.         ConfigurableApplicationContext context = SpringApplication.run(TestApplication.class, args); 
  10.         // 將ApplicationContext傳給FastTester 
  11.         FastTester.run(context); 
  12.     } 

 

(3)編寫需要依賴pandora容器的case

  1. /** 
  2.  * tip:添加注解及方法需要重新啟動應用 
  3.  * 
  4.  * @author QZJ 
  5.  * @date 2020-08-03 
  6.  */ 
  7. @Slf4j 
  8. public class BucketServiceTest { 
  9.  
  10.     @Autowired 
  11.     BucketService bucketService; 
  12.  
  13.     @Test 
  14.     public void testSaveBucketInfo() { 
  15.         BucketRequest bucketRequest = new BucketRequest(); 
  16.          
  17.         // 缺少參數(shù) 
  18.         bucketRequest.setAccessKeyId("123"); 
  19.         bucketRequest.setAccessKeySecret("123"); 
  20.         bucketRequest.setBucketDomain("123"); 
  21.         bucketRequest.setEndpoint("123"); 
  22.         bucketRequest.setRegionId("123"); 
  23.         bucketRequest.setRoleArn("123"); 
  24.         bucketRequest.setRoleSessionName("123"); 
  25.         Result<Long> result = bucketService.saveBucketInfo(bucketRequest); 
  26.         log.info("缺少參數(shù) result :{}", JSON.toJSONString(result)); 
  27.          
  28.         // bucketName 重復 
  29.         bucketRequest.setBucketName("video2sky"); 
  30.         result = bucketService.saveBucketInfo(bucketRequest); 
  31.         log.info("bucketName 重復 result :{}", JSON.toJSONString(result)); 
  32.          
  33.         // 正例(執(zhí)行后,則bucketName已存在,需更換bucketName) 
  34.         bucketRequest.setBucketName("12345"); 
  35.         result = bucketService.saveBucketInfo(bucketRequest); 
  36.         log.info("正例 result :{}", JSON.toJSONString(result)); 
  37.     } 
  38.      
  39.     @Test 
  40.     public void testCreateBucketFolder() { 
  41.         BucketFolderRequest bucketFolderRequest = new BucketFolderRequest(); 
  42.         bucketFolderRequest.setFolderPath("/test"); 
  43.         bucketFolderRequest.setAppName("wudao"); 
  44.         bucketFolderRequest.setDescription("data"); 
  45.         bucketFolderRequest.setWriteTokenExpireTime(3600L); 
  46.         Result<Long> result = bucketService.createBucketFolder(bucketFolderRequest); 
  47.         log.info("缺少參數(shù) result :{}", JSON.toJSONString(result)); 
  48.          
  49.         // 錯誤的bucketId 
  50.         bucketFolderRequest.setBucketId(1L); 
  51.         result = bucketService.createBucketFolder(bucketFolderRequest); 
  52.         log.info("錯誤的bucketId result :{}", JSON.toJSONString(result)); 
  53.          
  54.         // 異常的讀時間,讀寫時間不得超過2小時 
  55.         bucketFolderRequest.setWriteTokenExpireTime(7300L); 
  56.         result = bucketService.createBucketFolder(bucketFolderRequest); 
  57.         log.info("異常的讀時間 result :{}", JSON.toJSONString(result)); 
  58.          
  59.         // 重復的bucketFolder 
  60.         bucketFolderRequest.setBucketId(11L); 
  61.         bucketFolderRequest.setWriteTokenExpireTime(3500L); 
  62.         result = bucketService.createBucketFolder(bucketFolderRequest); 
  63.         log.info("重復的bucketFolder result :{}", JSON.toJSONString(result)); 
  64.          
  65.         // 正例 (本地與服務器默認文件地址不一致,所以本地無法執(zhí)行成功,除非改地址,或者添加分支代碼) 
  66.         bucketFolderRequest.setFolderPath("/test2"); 
  67.         result = bucketService.createBucketFolder(bucketFolderRequest); 
  68.         log.info("正例 result :{}", JSON.toJSONString(result)); 
  69.     } 

(4)啟動TestApplication,輸入對應類名,選擇要執(zhí)行的相應方法即可(切換測試類,直接重新輸入類路徑(包名+文件名)即可,原理還是反射)。

 

Tip:如果service注解失敗,檢查測試包的層級,例如:

 

Junit

JUnit是一個Java語言的單元測試框架, Junit測試是程序員測試,即所謂白盒測試,因為程序員知道被測試的軟件如何(How)完成功能和完成什么樣(What)的功能。繼承TestCase類,就可以用Junit進行自動測試。

來源:https://baike.baidu.com/item/白盒測試

使用方式:

(1)私有方法測試

  1. /** 
  2.  * 普通類測試,無需啟動容器 
  3.  * 
  4.  * @author QZJ 
  5.  * @date 2020-08-05 
  6.  */ 
  7. @Slf4j 
  8. public class OssServiceTest { 
  9.      
  10.     private OssServiceImpl ossService = new OssServiceImpl(); 
  11.  
  12.     @Test 
  13.     public void testCreateOssFolder() { 
  14.         try { 
  15.             // 私有方法測試:方法一:用反射(推薦);方法二:修改類中方法屬性(不推薦) 
  16.             Method method = OssServiceImpl.class.getDeclaredMethod("createOssFolder"
  17.                 new Class[] {OSS.class, String.class, String.class}); 
  18.             method.setAccessible(true); 
  19.             OSS client = new OSSClientBuilder().build("oss-cn-beijing.aliyuncs.com""**"
  20.                 "****"); 
  21.             Object obj = method.invoke(ossService, new Object[] {client, "***""wudao/test3"}); 
  22.             Assert.assertEquals(true, obj); 
  23.         } catch (Exception e) { 
  24.             Assert.fail("testCreateOssFolder fail"); 
  25.         } 
  26.     } 

(2)相關測試注解如@Ignore使用,相關屬性如timeout測試接口性能、expected異常期望返回結果使用,測試全部測試方法等。

  1. /** 
  2.  * 普通工具類測試 
  3.  * @author QZJ 
  4.  * @date 2020-08-05 
  5.  */ 
  6. @Slf4j 
  7. public class DateUtilTest { 
  8.  
  9.     @Ignore // 忽略該方法執(zhí)行結果 
  10.     @Test 
  11.     public void testGetCurrentTime(){ 
  12.         String dateStr = DateUtil.getCurrentTime("yyyy-MM-dd HH:mm"); 
  13.         log.info("date:{}", dateStr); 
  14.         Assert.assertEquals("2020-08-05 17:22", dateStr); 
  15.     } 
  16.  
  17.     // 方法超時時間設置以及期望執(zhí)行拋出的異常類型設置(錯誤的日期格式解析異常) 
  18.     @Test(timeout = 110L, expected = ParseException.class) 
  19.     public void testString2Date() throws ParseException{ 
  20.         Date date = DateUtil.string2Date("20202-02 02:02"); 
  21.         log.info("date:{}" , date); 
  22.         //Thread.sleep(200L); 
  23.     } 
  24.  
  25.     @BeforeClass 
  26.     public static void beforeClass() { 
  27.         log.info("before class"); 
  28.     } 
  29.  
  30.     @AfterClass 
  31.     public static void  afterClass() { 
  32.         log.info("after class"); 
  33.     } 
  34.  
  35.     @Before 
  36.     public void before() { 
  37.         log.info("before"); 
  38.     } 
  39.  
  40.     @After 
  41.     public void after() { 
  42.         log.info("after"); 
  43.     } 
  44.  
  45.     public static void main(String[] args) { 
  46.         // 不需啟動容器的情況下使用,跑類中所有case 
  47.         Result result = JUnitCore.runClasses(DateUtilTest.class); 
  48.         result.getFailures().stream().forEach(f -> System.out.println(f.toString())); 
  49.         log.info("result:{}", result.wasSuccessful()); 
  50.     } 

詳細使用文檔見:https://wiki.jikexueyuan.com/project/junit/environment-setup.html

Mockito

Mockito是一個針對Java的mocking框架,主要作用mock請求及返回值。

Mockito可以隔離類之間的相互依賴,做到真正的方法級別單測。

使用方式:

(1)需要引入jar包

  1. <dependency> 
  2.    <groupId>org.mockito</groupId> 
  3.    <artifactId>mockito-all</artifactId> 
  4.    <version>1.9.5</version> 
  5.    <scope>test</scope> 
  6. </dependency> 

(2)編寫測試代碼(例子)

需要測試的方法中調用了二方/三方接口,而接口無測試環(huán)境,為了測試方法邏輯,可以模擬接口返回結果(對原先代碼無侵入),達到應用內測試閉環(huán)。

tip:mock數(shù)據(jù)并非真正的返回值,需要注意返回的結果類型,字符串長度等,防止出現(xiàn)轉化,入庫字段超長等問題。

 

  1. @Override 
  2. public ConsumeCodeResult consumeCode(String code) { 
  3.     // 權益核銷 
  4.     if (code.startsWith(BENEFIT_CENTER_CODE_HEADER) && BENEFIT_CENTER_CODE_LENGTH == code.length()) { 
  5.         return consumeCodeFromCodeBenefitCenter(code); 
  6.     } 
  7.     // 碼商核銷 
  8.     return consumeCodeFromCodeCenter(code); 
  9.  
  10. /** 
  11.  * 從權益中心核銷電子憑證 
  12.  * 
  13.  * @param code 電子碼 
  14.  * @return 核銷結果 
  15.  */ 
  16. private ConsumeCodeResult consumeCodeFromCodeBenefitCenter(String code) { 
  17.     // 參數(shù)構造 
  18.     BenefitUseDTO benefitUseDTO = new BenefitUseDTO(); 
  19.     benefitUseDTO.setCouponCode(code); 
  20.     benefitUseDTO.getExtendFields().put("configId", benefitId); 
  21.     benefitUseDTO.getExtendFields().put("type", BenefitTypeEnum.CODE_GENERAL.getType().toString()); 
  22.     AlispResult alispResult = benefitService.useBenefit(benefitUseDTO); 
  23.     log.info("benefitUseDTO:{}, result:{}", benefitUseDTO, alispResult); 
  24.     if (alispResult.isSuccess()) { 
  25.         BenefitUseResult benefitUseResult = (BenefitUseResult)alispResult.getValue(); 
  26.         return new ConsumeCodeResult(benefitUseResult.getOutOrderId(), 
  27.             String.valueOf(benefitUseResult.getConfigId()), benefitUseResult.getUseTime()); 
  28.     } 
  29.     // 已使用 
  30.     if (BizErrorCodeEnum.BENEFIT_RECORD_USED.name().equals(alispResult.getErrCodeName())) { 
  31.         throw new BizException(StudentErrorEnum.VERIFICATION_CODE_REPEAT); 
  32.     } else if (BizErrorCodeEnum.BENEFIT_RECORD_NOT_EXIST.name().equals(alispResult.getErrCodeName()) 
  33.         || BizErrorCodeEnum.BENEFIT_RECORD_EXPIRED.name().equals(alispResult.getErrCodeName())) { 
  34.         // 不存在或者過期 
  35.         throw new BizException(StudentErrorEnum.VERIFICATION_CODE_INVALID); 
  36.     } else { 
  37.         // 其他異常 
  38.         throw new BizException(StudentErrorEnum.VERIFICATION_CODE_CONSUME_FAILED); 
  39.     } 

 

  1. @Test 
  2. public void mockConsume(){ 
  3.     BenefitService benefitService = Mockito.mock(BenefitService.class); 
  4.     // 核銷成功鏈路 
  5.     AlispResult alispResult = new AlispResult(true); 
  6.     BenefitUseResult benefitUseResult = new BenefitUseResult(); 
  7.     benefitUseResult.setConfigId(1L); 
  8.     benefitUseResult.setOutOrderId("lalala"); 
  9.     benefitUseResult.setUseTime(new Date()); 
  10.     alispResult.setValue(benefitUseResult); 
  11.  
  12.     Mockito.when(benefitService.useBenefit(Mockito.any(BenefitUseDTO.class))).thenReturn(alispResult); 
  13.     ConsumeCodeService consumeCodeService = new ConsumeCodeServiceImpl(benefitService, "1"); 
  14.     ConsumeCodeResult consumeCodeResult = consumeCodeService.consumeCode("082712345678"); 
  15.     System.out.println(JSON.toJSONString(consumeCodeResult)); 
  16.  
  17.     alispResult = new AlispResult(false); 
  18.     // 已核銷鏈路 
  19.     alispResult.setErrCodeName("BENEFIT_RECORD_USED"); 
  20.     // 已過期鏈路 
  21.     //alispResult.setErrCodeName("BENEFIT_RECORD_EXPIRED"); 
  22.     // 碼不存在鏈路 
  23.     //alispResult.setErrCodeName("BENEFIT_RECORD_NOT_EXIST"); 
  24.     // 其他返回錯誤 
  25.     //alispResult.setErrCodeName("LALALA"); 
  26.  
  27.     Mockito.when(benefitService.useBenefit(Mockito.any(BenefitUseDTO.class))).thenReturn(alispResult); 
  28.     consumeCodeService = new ConsumeCodeServiceImpl(benefitService, "1"); 
  29.     try { 
  30.         consumeCodeService.consumeCode("082712345678"); 
  31.     } catch (Exception e) { 
  32.         e.printStackTrace(); 
  33.     } 
  34.  
  35.     // 核銷碼頭有誤 
  36.     consumeCodeService = new ConsumeCodeServiceImpl(benefitService, "1"); 
  37.     try { 
  38.         consumeCodeService.consumeCode("081712345678"); 
  39.     } catch (Exception e) { 
  40.         e.printStackTrace(); 
  41.     } 
  42.     // 核銷碼長度有誤 
  43.     consumeCodeService = new ConsumeCodeServiceImpl(benefitService, "1"); 
  44.     try { 
  45.         consumeCodeService.consumeCode("08271234567"); 
  46.     } catch (Exception e) { 
  47.         e.printStackTrace(); 
  48.     } 

Mockito的功能非常多,可以驗證行為,做測試樁,匹配參數(shù),驗證調用次數(shù)和執(zhí)行順序等等,在這不一一枚舉了,更多詳細使用可見文檔:https://github.com/hehonghui/mockito-doc-zh

2 覆蓋率

覆蓋率是度量測試完整性的一個手段,是測試有效性的一個度量。

覆蓋率準則

  • 函式覆蓋率(Function coverage):有呼叫到程式中的每一個函式(或副程式)嗎?
  • 指令覆蓋率(Statement coverage):若用控制流圖(英語:control flow graph)表示程式,有執(zhí)行到控制流圖中的每一個節(jié)點嗎?
  • 判斷覆蓋率(Decision coverage):(和分支覆蓋率不同)若用控制流圖表示程式,有執(zhí)行到控制流圖中的每一個邊嗎?例如控制結構中所有IF指令都有執(zhí)行到邏輯運算式成立及不成立的情形嗎?
  • 條件覆蓋率(Condition coverage):也稱為謂詞覆蓋(predicate coverage),每一個邏輯運算式中的每一個條件(無法再分解的邏輯運算式)是否都有執(zhí)行到成立及不成立的情形嗎?條件覆蓋率成立不表示判斷覆蓋率一定成立。
  • 條件/判斷覆蓋率(Condition/decision coverage):需同時滿足判斷覆蓋率和條件覆蓋率。

場景總結

  • 必要的
  • 復雜的
  • 重要的
  • 不寫無用的

具體還需自己判斷,但是要避免過度自信。

覆蓋率要求

是否覆蓋率越高越好?回歸根本,我們寫單測的意義最重要的一點是為了保證代碼的正確性,如果我們把復雜的、重要的、必要的測試覆蓋到,即可保證應用的正確性,例如set、get方法,完全沒有必要寫單測,不必為了追求覆蓋率而刻意寫單測,尺度這個東西,無論何時何事都是要有分寸的。躬身入局,寫起來,會慢慢找到節(jié)奏的。

3 思想

測試工具是神兵利器,設計原則是內功心法,設計原則作為編寫代碼的指導思想,單元測試作為驗證代碼好壞的有效途徑,共同推動代碼演進。

6 最后

影響單測落地的原因:

 

  • 團隊無單測習慣,個人是否follow
  • 業(yè)務壓力大,覺得寫單測耗時
  • 覺得可有可無
  • 單測是一個程序員的自我修養(yǎng)

【本文為51CTO專欄作者“阿里巴巴官方技術”原創(chuàng)稿件,轉載請聯(lián)系原作者】

 

戳這里,看該作者更多好文

 

責任編輯:武曉燕 來源: 51CTO專欄
相關推薦

2022-08-26 08:53:46

單元測試校驗框架

2021-06-15 08:08:47

Java單元測試

2025-08-28 01:00:00

Go單元測試

2017-01-14 23:42:49

單元測試框架軟件測試

2011-04-18 13:20:40

單元測試軟件測試

2021-03-28 23:03:50

Python程序員編碼

2022-03-15 11:55:24

前端單元測試

2017-01-16 12:12:29

單元測試JUnit

2017-01-14 23:26:17

單元測試JUnit測試

2020-08-18 08:10:02

單元測試Java

2019-06-10 19:00:23

Cmain函數(shù)編程語言

2017-03-23 16:02:10

Mock技術單元測試

2021-05-05 11:38:40

TestNGPowerMock單元測試

2023-07-26 08:58:45

Golang單元測試

2020-05-07 17:30:49

開發(fā)iOS技術

2011-07-04 18:16:42

單元測試

2011-11-30 22:03:49

ibmdwJava

2021-09-18 15:40:03

Vue單元測試命令

2019-12-18 10:25:12

機器學習單元測試神經(jīng)網(wǎng)絡

2011-05-16 16:52:09

單元測試徹底測試
點贊
收藏

51CTO技術棧公眾號

亚洲免费在线观看| 亚洲女同中文字幕| 欧洲一区二区三区在线| 国产精品区一区二区三| 韩剧1988免费观看全集| 亚洲精品成人av久久| 国产精品毛片无码| 欧美性猛交xxxx乱大交蜜桃| 樱花www成人免费视频| 免费观看a视频| 日本成人超碰在线观看| 欧美福利视频网站| 欧美日韩生活片| 台湾亚洲精品一区二区tv| 欧美日韩黄色影视| 国产男女在线观看| 18av在线视频| 国产婷婷色一区二区三区在线| 亚洲精品免费网站| www.国产毛片| 亚洲天堂久久| 国产高清欧美| 88在线观看91蜜桃国自产| 免费看国产曰批40分钟| a毛片在线看免费观看| 国产欧美日韩激情| 国产一区二区无遮挡| 国产欧美熟妇另类久久久| 日韩精品色哟哟| 国产日韩亚洲欧美综合| 国产精品永久免费观看| 日干夜干天天干| 中文字幕亚洲综合久久五月天色无吗''| 亚洲人成电影网站色xx| 影音先锋资源av| 亚洲图片小说区| 欧美性猛交xxxx乱大交退制版| 黄页免费在线观看视频| 午夜伦理在线视频| 亚洲色图.com| 一区二区三区四区视频在线| 国产福利片在线| 91美女在线观看| 国产一区二区免费电影| 丰满人妻一区二区三区免费| 国产一区二区调教| 91久久精品国产91久久| 国产精品久久免费| 国产一区在线不卡| 91亚洲精品一区| 国产女人高潮时对白| 精品久久亚洲| 久久视频精品| 亚洲大胆人体在线| 国产一卡二卡三卡四卡| 成人看片爽爽爽| 亚洲第一男人天堂| 内射中出日韩无国产剧情| 噜噜噜狠狠夜夜躁精品仙踪林| 亚洲成人久久一区| 成人精品在线观看视频| 美女精品一区最新中文字幕一区二区三区 | 亚洲毛片亚洲毛片亚洲毛片| 成人情趣视频| 精品国产一区二区在线| 欧美爱爱免费视频| 欧美日韩hd| 亚州欧美日韩中文视频| 西西44rtwww国产精品| 久久av一区| 国产精品视频自在线| 一级久久久久久久| 国产高清久久久| 国产亚洲欧美一区二区三区| 性插视频在线观看| 日本一区二区三区四区| 免费久久久久久| 久久大胆人体| 一本到不卡精品视频在线观看| 成人中文字幕av| a一区二区三区亚洲| 精品粉嫩超白一线天av| 无码人妻丰满熟妇啪啪欧美| 婷婷激情图片久久| 性欧美视频videos6一9| 亚洲午夜无码久久久久| 国产一区欧美一区| 免费av在线一区二区| 日本综合在线| 日韩av在线免费观看不卡| 亚洲自拍偷拍欧美| 国产精品日韩三级| 国产精品扒开腿做爽爽爽视频软件| 欧美日韩欧美一区二区| 日本精品一二三区| 日韩欧美一区免费| 性亚洲最疯狂xxxx高清| 在线观看中文字幕码| av中文字幕不卡| 亚洲日本理论电影| 香蕉伊大人中文在线观看| 欧美日韩亚洲国产综合| 超碰caoprom| 91麻豆精品国产91久久久平台| 久久久亚洲欧洲日产国码aⅴ| 波多野结衣午夜| 韩日av一区二区| 国产伦精品一区二区三区视频黑人 | 亚洲精品永久免费视频| 欧美一区二区三区免费视频| 国产精品无码久久久久一区二区| 中文字幕亚洲综合久久五月天色无吗''| 欧美又大粗又爽又黄大片视频| 国产欧美久久久| 中文一区在线播放| 日韩欧美猛交xxxxx无码| jk漫画禁漫成人入口| 欧美狂野另类xxxxoooo| 这里只有久久精品| 国产精品久久久久久久久久妞妞 | 成人免费在线视频| 7777精品伊人久久久大香线蕉完整版| 国产精品久久久久9999高清| 成人欧美一区二区三区在线| 免费一级毛片在线观看| 亚洲一区二区高清| 精品三级av在线| 国内精品国产三级国产aⅴ久| 神马久久一区二区三区| 91精品国产沙发| av综合在线观看| 国产精品免费人成网站| av免费网站观看| 亚洲精品亚洲人成在线观看| 国内精品久久久久久影视8| www日本高清视频| 亚洲欧美在线视频| jizz欧美性11| 日韩大片在线| 国产欧美精品一区二区| av网站在线免费播放| 色婷婷综合久久久久中文| 亚洲专区区免费| 国产精品久久久免费| 精品无人区一区二区三区竹菊| 女囚岛在线观看| 日韩女优av电影| 免费一级肉体全黄毛片| 国产成人综合亚洲91猫咪| 亚洲免费视频播放| 久久国产精品免费一区二区三区| 欧美成aaa人片免费看| av一级黄色片| 亚洲成人免费视| 桥本有菜av在线| 好吊色一区二区| 亚洲精品成人在线| 妖精视频在线观看| 欧美精品一区二区三区久久久竹菊| 成人精品福利视频| 性欧美高清come| 欧美揉bbbbb揉bbbbb| 亚洲色图欧美色| 久久国产精品72免费观看| 一区在线电影| 中文字幕综合| 欧美激情性做爰免费视频| 亚洲国产一二三区| 偷窥国产亚洲免费视频| 亚洲精品国产一区黑色丝袜| 可以看av的网站久久看| 亚洲精美视频| 国产一区二区三区| 久久综合九色| 国产中文字幕91| 国产高清一区二区三区视频| 欧美一区二区视频在线观看2022 | 日本一区二区综合亚洲| 国产精品久久a| 好吊一区二区三区| 日本不卡二区| 国产精品日韩精品在线播放| 久久久在线观看| 好男人免费精品视频| 337p亚洲精品色噜噜| 九九热国产视频| 国产欧美综合色| 国产又黄又嫩又滑又白| 久久久久国产精品一区二区| 在线一区高清| 欧美男人操女人视频| 国产深夜精品福利| 麻豆免费在线| 久久久精品2019中文字幕神马| 亚洲精品18在线观看| 91福利精品第一导航| 天天干中文字幕| 国产偷国产偷精品高清尤物| 亚洲av综合色区无码另类小说| 日韩在线一区二区| 日韩极品视频在线观看 | 日本一二三区视频在线| 久久av综合| 国产精品国模大尺度私拍| 国产精品videossex撒尿| 久久噜噜噜精品国产亚洲综合| 91社区在线高清| 亚洲国产精品va在线看黑人| 在线观看日批视频| 欧美性猛交xxxx免费看| 久久无码精品丰满人妻| 国产精品灌醉下药二区| 中文字幕在线观看网址| 国产一区二区导航在线播放| 国产精品入口免费软件| av成人天堂| 免费高清一区二区三区| 婷婷亚洲综合| 视频一区二区精品| 亚洲国产精品嫩草影院久久av| 久久久精品黄色| 热久久精品国产| 99精品免费| 免费极品av一视觉盛宴| 久久综合国产| 日韩亚洲不卡在线| 亚洲欧美成人vr| 精品一区二区国产| 国产精品22p| 97人人澡人人爽| 精品国产不卡一区二区| 国产一区视频在线| 国产黄色精品| 国产精品无av码在线观看| gogo亚洲高清大胆美女人体| 青青青国产精品一区二区| 欲香欲色天天天综合和网| 性色av一区二区三区在线观看| 成人av影院在线观看| 国产69精品久久久| а√天堂中文资源在线bt| 久久久久久久久久久久久久久久久久av| 18网站在线观看| 欧美国产日韩视频| 三级网站视频在在线播放| 欧美大片免费观看| 黑人玩欧美人三根一起进| 欧美大片免费看| a天堂资源在线| 4438全国成人免费| 美女100%一区| 国产精品日韩在线一区| 国产69精品久久| 国产日韩视频在线观看| 不卡一区视频| 99视频免费观看| 美女av一区| 欧美性xxxx69| 日韩久久精品网| 国产精品一二三在线观看| 欧美午夜电影在线观看| 日本在线xxx| 久久狠狠婷婷| 成人不卡免费视频| 国产福利一区二区三区视频在线| 尤物网站在线观看| 国产亚洲一二三区| 99久久99久久精品国产| 亚洲va国产va欧美va观看| 欧美精品一二三四区| 欧美日韩三级视频| 精品国产区一区二| 日韩精品丝袜在线| japanese在线视频| 色综合中文网| 91制片厂免费观看| 国产精品日韩久久久| 99热手机在线| 国产丶欧美丶日本不卡视频| 中文字幕5566| 中文字幕中文字幕在线一区| 黄色一级片在线免费观看| 日韩欧美在线视频| 国产免费久久久| 精品亚洲永久免费精品| 一区二区三区视频在线观看视频| 欧美国产日韩xxxxx| 二吊插入一穴一区二区| 99re在线视频上| 国产欧美高清视频在线| 久久av喷吹av高潮av| 一本色道久久综合一区| 做a视频在线观看| 99久久综合狠狠综合久久| 性爱在线免费视频| 偷拍亚洲欧洲综合| 国产美女主播在线观看| 亚洲欧美中文日韩v在线观看| 国内外激情在线| 日本精品va在线观看| 色妞ww精品视频7777| 日韩欧美一区二区视频在线播放 | 久久夜色精品国产亚洲aⅴ| 成人国产电影在线观看| 成人黄色片网站| 精品理论电影在线| 免费av手机在线观看| 极品少妇xxxx精品少妇偷拍| 永久免费看mv网站入口78| 亚洲国产精品久久人人爱蜜臀| 91精品国产乱码久久久久| 亚洲欧洲成视频免费观看| 久久av色综合| 亚洲自拍欧美色图| 99久久www免费| 福利在线一区二区三区| 91免费版在线| 欧美精品亚洲精品日韩精品| 欧美电视剧在线看免费| 麻豆网站在线免费观看| 国产精品久久久一区| 蜜桃精品wwwmitaows| www一区二区www免费| 丁香网亚洲国际| 青娱乐免费在线视频| 欧美一区二区在线看| 午夜视频在线观看网站| 国产精品久久久久久亚洲调教| 国产chinese中国hdxxxx| 国产日韩欧美制服另类| 四虎成人永久免费视频| a亚洲天堂av| www.激情网| 美洲天堂一区二卡三卡四卡视频 | 国产精品午夜春色av| 欧美国产成人精品一区二区三区| 亚洲精品电影网| aa国产成人| 国产一区在线观| 亚洲经典在线| 一女三黑人理论片在线| 亚州成人在线电影| 香蕉av在线播放| 69av成年福利视频| 亚洲丁香日韩| 欧美日韩在线成人| 国产欧美va欧美不卡在线 | 欧美一卡2卡三卡4卡5免费| 国产不卡在线| 91免费观看| 1024成人| 久久一区二区电影| 一本色道久久综合亚洲aⅴ蜜桃| 青青草在线免费视频| 国产精品成人aaaaa网站| 精品欧美久久| 中日韩av在线播放| 一区二区三区四区五区视频在线观看| 国产99999| 韩剧1988在线观看免费完整版| 特黄特色欧美大片| 中文字幕第36页| 亚洲精选在线视频| 欧美一级特黄aaaaaa大片在线观看| 97人人模人人爽人人喊中文字| 欧美一性一交| 自拍偷拍 国产| 亚洲欧美经典视频| 污污视频在线免费看| 国产成人啪精品视频免费网| 日韩欧美二区| 日韩精品国产一区| 色女孩综合影院| a级片国产精品自在拍在线播放| 国产精品久久久久久久久久久久冷| 亚洲综合99| 国产又粗又猛又爽又黄的视频小说| 日韩一区二区影院| 久久毛片亚洲| 四虎免费在线观看视频| 97精品国产露脸对白| 91高潮大合集爽到抽搐| 韩日精品中文字幕| 婷婷另类小说| 国产高清自拍视频| 欧美高清视频不卡网| 亚洲黄色中文字幕| 特大黑人娇小亚洲女mp4| 91玉足脚交白嫩脚丫在线播放| 亚洲视频久久久| 97婷婷涩涩精品一区| 999久久久精品国产| 3d精品h动漫啪啪一区二区 | 一区二区三区四区视频精品免费 | 亚洲国产经典视频| 亚洲av无码一区二区三区性色| 日韩av电影免费观看高清| 无需播放器亚洲| 91精彩刺激对白露脸偷拍| 日韩视频在线永久播放|