
…并測試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》 。
那么,模擬對象與存根有何不同? 當您聽到人們談論模擬對象時,他們經常提到他們在嘲笑 行為或嘲笑 角色 ,但這意味著什么? 答案在于單元測試和模擬對象共同測試對象的方式。 模擬對象場景如下所示:
- 測試中定義了一個模擬對象。
- 模擬對象被注入到您的測試對象中
- 該測試指定將調用模擬對象上的哪些方法,以及參數和返回值。 這就是所謂的“ 設定期望 ”。
- 然后運行測試。
- 然后,測試將要求模擬程序驗證步驟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