
…并且我提出了這樣的想法:任何不包含任何邏輯的類都不需要進行單元測試。 在其中,我包括了我的數據訪問對象DAO,而不是對此類進行集成測試以確保其與數據庫協同工作。
今天的博客涵蓋編寫常規或經典的單元測試,該測試使用存根對象強制實現測試主題隔離。 我們將要測試的代碼再次是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;}
}
邁克爾·費瑟(Michael Feather)的書《有效使用舊版代碼》指出,在以下情況下,測試不是單元測試:
- 它與數據庫對話。
- 它通過網絡進行通信。
- 它涉及文件系統。
- 您必須對環境做一些特殊的事情(例如編輯配置文件)才能運行它。
為了遵守這些規則,您需要將測試對象與系統的其余部分隔離開來,這就是存根對象的所在。存根對象是注入到您的對象中的對象,用于在測試情況下替換實際對象。 馬丁·福勒(Martin Fowler)在他的論文《 莫克斯不是存根》中將存根定義為:
“存根提供對測試過程中進行的呼叫的固定答復,通常通常根本不響應測試中編程的內容。 存根還可以記錄有關呼叫的信息,例如電子郵件網關存根,它可以記住“已發送”的消息,或者僅記住“已發送”的消息數量。”
用一個單詞來描述存根非常困難,我可以選擇虛擬或偽造,但是有些替換對象稱為假人或偽造-也由Martin Fowler描述:
- 虛擬對象會傳遞,但從未實際使用過。 通常它們僅用于填充參數列表。
- 偽對象實際上具有有效的實現,但是通常采取一些捷徑,這使它們不適合生產(內存數據庫是一個很好的例子)。
但是,我看到過其他術語“偽造對象”的定義,例如Roy Osherove在《 The Art Of Unit Testing》一書中將偽造對象定義為:
- 偽造品是一個通用術語,可用于描述存根或模擬對象……因為兩者看起來都像真實對象。
…因此,我和其他許多人一樣,傾向于將所有替換對象稱為模擬或存根,因為兩者之間存在差異,但稍后會更多。
在測試AddressService時 ,我們需要用存根數據訪問對象替換實際的數據訪問對象,在這種情況下,它看起來像這樣:
public class StubAddressDao implements AddressDao {private final Address address;public StubAddressDao(Address address) {this.address = address;}/*** @see com.captaindebug.address.AddressDao#findAddress(int)*/@Overridepublic Address findAddress(int id) {return address;}
}
注意存根代碼的簡單性。 它應該易于閱讀,可維護,并且不包含任何邏輯,并且需要自己進行單元測試。 編寫存根代碼后,接下來進行單元測試:
public class ClassicAddressServiceWithStubTest {private AddressService instance;@Beforepublic void setUp() throws Exception {/* Create the object to test *//* Setup data that's used by ALL tests in this class */instance = new AddressService();}/*** Test method for* {@link com.captaindebug.address.AddressService#findAddress(int)}.*/@Testpublic void testFindAddressWithStub() {/* Setup the test data - stuff that's specific to this test */Address expectedAddress = new Address(1, "15 My Street", "My Town","POSTCODE", "My Country");instance.setAddressDao(new StubAddressDao(expectedAddress));/* Run the test */Address result = instance.findAddress(1);/* Assert the results */assertEquals(expectedAddress.getId(), result.getId());assertEquals(expectedAddress.getStreet(), result.getStreet());assertEquals(expectedAddress.getTown(), result.getTown());assertEquals(expectedAddress.getPostCode(), result.getPostCode());assertEquals(expectedAddress.getCountry(), result.getCountry());}@Afterpublic void tearDown() {/** Clear up to ensure all tests in the class are isolated from each* other.*/}
}
請注意,在編寫單元測試時,我們的目標是清晰。 通常會犯一個錯誤,就是認為測試代碼不如生產代碼,從而導致測試代碼更加混亂且難以辨認。 單元測試的藝術中的 Roy Osherove提出了這樣的想法,即測試代碼應比生產代碼更具可讀性。 明確的測試應遵循以下基本的線性步驟:
- 創建被測對象。 在上面的代碼中,這是在setUp()方法中完成的,因為我正在對所有(一個)測試使用相同的被測對象。
- 設置測試。 這是在測試方法testFindAddressWithStub()中完成的,因為測試中使用的數據特定于該測試。
- 運行測試
- 撕毀測試。 這樣可以確保測試彼此隔離,并且可以按任何順序運行。
使用簡單的存根會產生將AddressService與外界隔離和快速運行測試的兩個好處。
這種測試有多脆? 如果您的要求改變了,那么測試和存根就會改變–畢竟不是那么脆弱嗎?
作為比較,我的下一個博客使用EasyMock重寫了該測試。
參考: 定期的單元測試和存根-來自JCG合作伙伴
的Captain Debug博客上的 測試技術4相關文章 :
- 測試技巧–不編寫測試
- 端到端測試的濫用–測試技術2
- 您應該對什么進行單元測試? –測試技術3
- 使用模擬的單元測試–測試技術5
- 為舊版代碼創建存根–測試技術6
- 有關為舊版代碼創建存根的更多信息–測試技術7
- 為什么要編寫單元測試–測試技巧8
- 一些定義–測試技術9
- 使用FindBugs產生更少的錯誤代碼
- 在云中開發和測試
翻譯自: https://www.javacodegeeks.com/2011/11/regular-unit-tests-and-stubs-testing.html