使用模擬的單元測試–測試技術5

我的最后一個博客是有關測試代碼方法的一系列博客中的第四篇,演示了如何創建使用存根對象隔離測試對象的單元測試。 今天的博客探討了有時被視為對立的技術:使用模擬對象進行單元測試。 同樣,我使用了從數據庫檢索地址的簡單方案:

…并測試AddressService類:

@Component
public class AddressService {private static final Logger logger = LoggerFactory.getLogger(AddressService.class);private AddressDao addressDao;/*** Given an id, retrieve an address. Apply phony business rules.* * @param id*            The id of the address object.*/public Address findAddress(int id) {logger.info("In Address Service with id: " + id);Address address = addressDao.findAddress(id);address = businessMethod(address);logger.info("Leaving Address Service with id: " + id);return address;}private Address businessMethod(Address address) {logger.info("in business method");// Apply the Special Case Pattern (See MartinFowler.com)if (isNull(address)) {address = Address.INVALID_ADDRESS;}// Do some jiggery-pokery here....return address;}private boolean isNull(Object obj) {return obj == null;}@Autowired@Qualifier("addressDao")void setAddressDao(AddressDao addressDao) {this.addressDao = addressDao;}
}

…通過將他的數據訪問對象替換為模擬對象。

在繼續之前,最好定義一個模擬對象的確切含義以及它與存根的不同之處。 如果您閱讀了我的上一篇博客,您會記得我讓Martin Fowler將存根對象定義為:

“存根提供對測試過程中進行的呼叫的固定答復,通常通常根本不響應測試中編程的內容。”

……摘自他的論文《 Mocks Are n't Stubs》 。

那么,模擬對象與存根有何不同? 當您聽到人們談論模擬對象時,他們經常提到他們在嘲笑 行為嘲笑 角色 ,但這意味著什么? 答案在于單元測試和模擬對象共同測試對象的方式。 模擬對象場景如下所示:

  1. 測試中定義了一個模擬對象。
  2. 模擬對象被注入到您的測試對象中
  3. 該測試指定將調用模擬對象上的哪些方法,以及參數和返回值。 這就是所謂的“ 設定期望 ”。
  4. 然后運行測試。
  5. 然后,測試將要求模擬程序驗證步驟3中指定的所有方法調用均已正確調用。 如果是,則測試通過。 如果不是,那么測試將失敗。

因此,模擬行為或模擬角色實際上意味著檢查被測對象是否正確調用了模擬對象上的方法,如果沒有,則使測試失敗。 因此,您是在斷言方法調用的正確性和通過代碼的執行路徑,而不是在常規單元測試的情況下斷言被測試方法的返回值。

盡管有幾種專業的模擬框架,但在本例中,我首先決定產生自己的AddressDao模擬,它可以滿足上述要求。 畢竟,這有多難?

public class HomeMadeMockDao implements AddressDao {/** The return value for the findAddress method */private Address expectedReturn;/** The expected arg value for the findAddress method */private int expectedId;/** The actual arg value passed in when the test runs */private int actualId;/** used to verify that the findAddress method has been called */private boolean called;/*** Set and expectation: the return value for the findAddress method*/public void setExpectationReturnValue(Address expectedReturn) {this.expectedReturn = expectedReturn;}public void setExpectationInputArg(int expectedId) {this.expectedId = expectedId;}/*** Verify that the expectations have been met*/public void verify() {assertTrue(called);assertEquals("Invalid arg. Expected: " + expectedId + " actual: " + expectedId, expectedId, actualId);}/*** The mock method - this is what we're mocking.* * @see com.captaindebug.address.AddressDao#findAddress(int)*/@Overridepublic Address findAddress(int id) {called = true;actualId = id;return expectedReturn;}
}

支持此模擬的單元測試代碼為:

public class MockingAddressServiceWithHomeMadeMockTest {/** The object to test */private AddressService instance;/*** We've written a mock,,,*/private HomeMadeMockDao mockDao;@Beforepublic void setUp() throws Exception {/* Create the object to test and the mock */instance = new AddressService();mockDao = new HomeMadeMockDao();/* Inject the mock dependency */instance.setAddressDao(mockDao);}/*** Test method for* {@link com.captaindebug.address.AddressService#findAddress(int)}.*/@Testpublic void testFindAddressWithEasyMock() {/* Setup the test data - stuff that's specific to this test */final int id = 1;Address expectedAddress = new Address(id, "15 My Street", "My Town", "POSTCODE", "My Country");/* Set the Mock Expectations */mockDao.setExpectationInputArg(id);mockDao.setExpectationReturnValue(expectedAddress);/* Run the test */instance.findAddress(id);/* Verify that the mock's expectations were met */mockDao.verify();}
}

好的,盡管這演示了使用模擬對象執行單元測試所需的步驟,但它相當粗糙且準備就緒,并且非常針對AddressDao / AddressService場景。 為了證明它已經做得更好,下面的示例使用easyMock作為模擬框架。 在這種更專業的情況下,單元測試代碼為:

@RunWith(UnitilsJUnit4TestClassRunner.class)
public class MockingAddressServiceWithEasyMockTest {/** The object to test */private AddressService instance;/*** EasyMock creates the mock object*/@Mockprivate AddressDao mockDao;/*** @throws java.lang.Exception*/@Beforepublic void setUp() throws Exception {/* Create the object to test */instance = new AddressService();}/*** Test method for* {@link com.captaindebug.address.AddressService#findAddress(int)}.*/@Testpublic void testFindAddressWithEasyMock() {/* Inject the mock dependency */instance.setAddressDao(mockDao);/* Setup the test data - stuff that's specific to this test */final int id = 1;Address expectedAddress = new Address(id, "15 My Street", "My Town", "POSTCODE", "My Country");/* Set the expectations */expect(mockDao.findAddress(id)).andReturn(expectedAddress);replay();/* Run the test */instance.findAddress(id);/* Verify that the mock's expectations were met */verify();}
}

…我希望您會同意,這比我快速嘗試編寫模擬游戲更具進步性。

使用模擬對象的主要批評是它們將單元測試代碼與生產代碼的實現緊密耦合。 這是因為設置期望值的代碼緊密跟蹤生產代碼的執行路徑。 這意味著即使該類仍履行其接口協定,后續對生產代碼的重構也可能破壞大量測試。 這引起了這樣的斷言,即模擬測試相當脆弱,并且您將花費不必要的時間修復它們,根據我的經驗,盡管我使用了“非嚴格”模擬,但這種模擬并不關心方法的順序,盡管我同意期望被稱為,在一定程度上減輕了問題。

另一方面,一旦您知道如何使用諸如easyMock之類的框架,就可以非常快速有效地完成將您的對象隔離的單元測試。

在自我批評該示例代碼時,我想指出的是,我認為在這種情況下使用模擬對象是過大的,此外,您還可以輕易地認為我將模擬作為存根使用。

幾年前,當我第一次遇到easyMock時,我在各處使用了模擬,但是最近我開始更喜歡手動為應用程序邊界類(例如DAO)和僅返回數據的對象編寫存根。 這是因為基于存根的測試可以說比基于模擬的測試要脆弱得多,尤其是當您需要訪問數據時。

為什么要使用模擬? 擅長測試使用“ 告訴不要詢問 ”技術編寫的應用程序,以驗證是否調用了具有無效返回值的方法。

參考: Captain Debug博客上來自JCG合作伙伴 使用Mocks進行單元測試-測試技術5

相關文章 :

  • 測試技巧–不編寫測試
  • 端到端測試的濫用–測試技術2
  • 您應該對什么進行單元測試? –測試技術3
  • 常規單元測試和存根–測??試技術4
  • 為舊版代碼創建存根–測試技術6
  • 有關為舊版代碼創建存根的更多信息–測試技術7
  • 為什么要編寫單元測試–測試技巧8
  • 一些定義–測試技術9
  • 使用FindBugs產生更少的錯誤代碼
  • 在云中開發和測試

翻譯自: https://www.javacodegeeks.com/2011/11/unit-testing-using-mocks-testing.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/374444.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/374444.shtml
英文地址,請注明出處:http://en.pswp.cn/news/374444.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

多線程中的volatile和偽共享

偽共享 false sharing,顧名思義,“偽共享”就是“其實不是共享”。那什么是“共享”?多CPU同時訪問同一塊內存區域就是“共享”,就會產生沖突,需要控制協議來協調訪問。會引起“共享”的最小內存區域大小就是一個cache…

C語言代碼規范(一)縮進與換行

一、縮進的空格數為4個。最好配置代碼編輯器將TAB鍵設置為空格替換&#xff0c;避免出現另一個編輯器打開時格式變亂的情況。 例如Notepad設置 KEIL設置 二、“{” 和 “}”各自獨占一行。 不規范例子&#xff1a; for(i 0; i < student_num; i) { if((score[i] > 0…

armv7 cortex a系列編程手冊_AWTK能為現代GUI編程帶來何種改變?

AWTK是一個伸縮性極強的嵌入式圖形框架&#xff0c;它的誕生會給GUI編程研發工程師帶來哪些改變&#xff1f;AWTK是一個伸縮性極強的嵌入式圖形框架&#xff0c;可在Cortex-M3這樣低端的單片機上運行&#xff0c;也可以在Cortex-A7/A8/A9等處理器&#xff0c;甚至DSP以及X86處理…

【轉】各種概念POJO、JAVABEAN、DAO、DTO、PO、VO、BO、SSH、EJB

POJO&#xff08;pure old java object&#xff09; 是普通java類&#xff0c;有一些private的參數作為對象的屬性&#xff0c;然后針對每一個參數定義get和set方法訪問的接口。我看到這個定義&#xff0c;心里就有個疑問了&#xff0c;這個POJO跟JavaBean的定義怎么就這么像&a…

為什么要編寫單元測試–測試技巧8

我對最近在“您應該測試什么”上的博客有很多反應&#xff0c;有些人出于各種原因同意我的想法&#xff0c;另一些人則認為建議某些類可能不需要單元測試是非常危險的。 已經處理了什么測試&#xff0c;今天的博客涉及為什么要編寫單元測試&#xff0c;而今天的示例代碼是基于一…

Git遷移 從SVN到Git

Migrating from SVN to Git 首先我們需要在Stach或者GitHub上新建一個Repository, 拿到它的URL。 接下來參照如下步驟 : At first we should create a new git repository at Stash and get the repository URL, and then follow below steps: 1. 切換到本地git工作目錄 chang…

C語言代碼規范(二)空格

一、逗號, 之后加空格 printf("error! score[%d] %d\n", i, score[i]); 二、分號; 之后加空格 for(i 0; i < student_num; i) 三、關系運算符<、<、>、>、、! 前后加空格 if( (score[i] > 0) && (score[i] < 100) ) 四、賦值運算符…

c++ 多重背包狀態轉移方程_動態規劃入門——詳解經典問題零一背包

本文始發于個人公眾號&#xff1a;TechFlow&#xff0c;原創不易&#xff0c;求個關注今天是周三算法與數據結構專題的第12篇文章&#xff0c;動態規劃之零一背包問題。在之前的文章當中&#xff0c;我們一起探討了二分、貪心、排序和搜索算法&#xff0c;今天我們來看另一個非…

Discuz! 的編碼規范

前言 本規范由編程原則組成&#xff0c;融合并提煉了開發人員長時間積累下來的成熟經驗&#xff0c;意在幫助形成良好一致的編程風格。適用范圍 如無特殊說明&#xff0c;以下規則要求完全適用于Discuz!項目&#xff0c;同時也可大部分適用于COMSENZ旗下其他PHP項目。標準化的重…

C語言代碼規范(三)if語句

一、整型變量與0比較 許多人為了一時之便&#xff0c;模仿布爾變量風格寫為如下代碼 if(value) {... }if(!value) {... } 應當用 或 ! 來與0比較 if(0 value) {... }if(0 ! value) {... } 二、當if內的語句是與常量進行比較時&#xff0c;常量為左值&#xff0c;變量為右…

6月24 面向對象的設計原則-----工廠模式和單列模式

工廠模式&#xff1a; 工廠模式就是專門負責將大量有共同接口的類實例化&#xff0c;而且不必事先知道每次是要實例化哪一個類的模式。它定義一個用于創建對象的接口&#xff0c;由子類決定實例化哪一個類。 工廠模式相當于創建實例對象的new&#xff0c;經常要根據類Class生成…

LeetCode Subsets

原題鏈接在這里&#xff1a;https://leetcode.com/problems/subsets/ 題目&#xff1a; Given a set of distinct integers, nums, return all possible subsets. Note: Elements in a subset must be in non-descending order.The solution set must not contain duplicate su…

使用ThreadPoolExecutor并行化獨立的單線程任務

Java SE 5.0中引入的任務執行框架是簡化多線程應用程序的設計和開發的巨大飛躍。 該框架提供了用于管理任務概念&#xff0c;管理線程生命周期及其執行策略的工具。 在此博客文章中&#xff0c;我們將描述該框架的功能&#xff0c;靈活性和簡單性&#xff0c;以展示一個簡單的用…

python定義一個圓_Python-矩形和圓形

原博文 2019-11-11 12:34 ? Exercise 15.1. 定義一個叫做Circle 類&#xff0c;類的屬性是圓心 (center) 和半徑 (radius) , 其中&#xff0c;圓心 (center) 是一個 Point 類&#xff0c;而半徑 (radius) 是一個數字。 實例化一個圓心 (center) 為 (150, 100) &#xff0c;半…

C語言代碼規范(四)命名規則

一、宏定義全部字母大寫&#xff0c;單詞間下劃線間隔 #define FLASH_PAGE_SIZE 256 #define FLASH_SECTOR_SIZE (4 * 1024) #define FLASH_BLOCK_SIZE (64 * 1024) #define FLASH_SIZE (16 * 1024 * 1024) 二、const修飾的常量全部字母大寫&#xff0c;單詞間…

Forbidden You don't have permission to access / on this server PHP

Forbidden You dont have permission to access / on this server PHP 在新安裝的谷歌游覽器里&#xff0c;打不了PHP網站了&#xff0c;錯誤顯示&#xff1a; Forbidden You dont have permission to access / on this server. 原因還是配置權限問題 解決辦法&#xff1a; wa…

Spring 3.1和JPA的持久層

1.概述 本教程顯示了如何使用Hibernate作為持久性提供程序使用JPA設置Spring 。 有關使用基于Java的配置和項目的基本Maven pom設置Spring上下文的分步介紹&#xff0c;請參閱本文 。 2. Java的JPA Spring配置 要在Spring項目中使用JPA&#xff0c; 需要設置EntityManager 。…

150928錯誤認識

1. $arr array(); foreach ($re as $k>$v){  $arr[] $v[updatetime];} $arr的返回結果為&#xff1a; Array ([0] > 2014-09[1] > 2015-04[2] > 2015-09 )$arr array(); foreach ($re as $k>$v){  $arr[$k] $v[updatetime];} $arr的返回結果為&#xff…

STM32F1筆記(一)GPIO輸出

GPIO&#xff1a;General Purpose Input Output &#xff08;通用輸入/輸出&#xff09;。 GPIO最經典應用&#xff1a;LED燈。 先看電路。聲明&#xff1a;參考正點原子戰艦開發板。 與LED串聯的電阻稱為限流電阻。 限流電阻計算公式&#xff1a;R(U-LED壓降)/20ma。 U為LE…

dataframe轉化為array_【Python專欄】12 種高效 Numpy 和 Pandas 函數為你加速分析

來源&#xff1a;機器之心編譯&#xff1a;Jamin、杜偉、張倩我們都知道&#xff0c;Numpy 是 Python 環境下的擴展程序庫&#xff0c;支持大量的維度數組和矩陣運算&#xff1b;Pandas 也是 Python 環境下的數據操作和分析軟件包&#xff0c;以及強大的數據分析庫。二者在日常…