新聞中心
自動化測試中,我們常會使用一些經(jīng)過簡化的,行為與表現(xiàn)類似于生產(chǎn)環(huán)境下的對象的復(fù)制品。引入這樣的復(fù)制品能夠降低構(gòu)建測試用例的復(fù)雜度,允許我們獨立而解耦地測試某個模塊,不再擔心受到系統(tǒng)中其他部分的影響;這類型對象也就是所謂的 Test Double。實際上對于 Test Double 的定義與闡述也是見仁見智,Gerard Meszaros 在這篇文章中就介紹了五個不同的 Double 類型;而人們更傾向于使用 Mock 來統(tǒng)一描述不同的 Test Doubles。不過對于 Test Doubles 實現(xiàn)的誤解還是可能會影響到測試的設(shè)計,使測試用例變得混亂和脆弱,最終帶來不必要的重構(gòu)。本文則是從作者個人的角度描述了常見的 Test Doubles 類型及其具體的實現(xiàn):Fake、Stub 與 Mock,并且給出了不同的 Double 的使用場景。

成都創(chuàng)新互聯(lián)公司是少有的網(wǎng)站建設(shè)、網(wǎng)站設(shè)計、營銷型企業(yè)網(wǎng)站、小程序開發(fā)、手機APP,開發(fā)、制作、設(shè)計、買友情鏈接、推廣優(yōu)化一站式服務(wù)網(wǎng)絡(luò)公司,從2013年成立,堅持透明化,價格低,無套路經(jīng)營理念。讓網(wǎng)頁驚喜每一位訪客多年來深受用戶好評
Fake
- Fakes are objects that have working implementations, but not same as production one. Usually they take some shortcut and have simplified version of production code.Fake 是那些包含了生產(chǎn)環(huán)境下具體實現(xiàn)的簡化版本的對象。
如下圖所示,F(xiàn)ake 可以是某個 Data Access Object 或者 Repository 的基于內(nèi)存的實現(xiàn);該實現(xiàn)并不會真的去進行數(shù)據(jù)庫操作,而是使用簡單的 HashMap 來存放數(shù)據(jù)。這就允許了我們能夠在并沒有真的啟動數(shù)據(jù)庫或者執(zhí)行耗時的外部請求的情況下進行服務(wù)的測試。
- @Profile("transient")
- public class FakeAccountRepository implements AccountRepository {
- Map
accounts = new HashMap<>(); - public FakeAccountRepository() {
- this.accounts.put(new User("[email protected]"), new UserAccount());
- this.accounts.put(new User("[email protected]"), new AdminAccount());
- }
- String getPasswordHash(User user) {
- return accounts.get(user).getPasswordHash();
- }
- }
除了應(yīng)用到測試,F(xiàn)ake 還能夠用于進行原型設(shè)計或者峰值模擬中;我們能夠迅速地實現(xiàn)系統(tǒng)原型,并且基于內(nèi)存存儲來運行整個系統(tǒng),推遲有關(guān)數(shù)據(jù)庫設(shè)計所用到的一些決定。另一個常見的使用場景就是利用 Fake 來保證在測試環(huán)境下支付永遠返回成功結(jié)果。
Stub
- Stub is an object that holds predefined data and uses it to answer calls during tests. It is used when we cannot or don’t want to involve objects that would answer with real data or have undesirable side effects.Stub 代指那些包含了預(yù)定義好的數(shù)據(jù)并且在測試時返回給調(diào)用者的對象。Stub 常被用于我們不希望返回真實數(shù)據(jù)或者造成其他副作用的場景。
Stub 的典型應(yīng)用場景即是當某個對象需要從數(shù)據(jù)庫抓取數(shù)據(jù)時,我們并不需要真實地與數(shù)據(jù)庫進行交互或者像 Fake 那樣從內(nèi)存中抓取數(shù)據(jù),而是直接返回預(yù)定義好的數(shù)據(jù)。
- public class GradesService {
- private final Gradebook gradebook;
- public GradesService(Gradebook gradebook) {
- this.gradebook = gradebook;
- }
- Double averageGrades(Student student) {
- return average(gradebook.gradesFor(student));
- }
- }
我們在編寫測試用例時并沒有從 Gradebook 存儲中抓取數(shù)據(jù),而是在 Stub 中直接定義好需要返回的成績列表;我們只需要足夠的數(shù)據(jù)來保證對平均值計算函數(shù)進行測試就好了。
- public class GradesServiceTest {
- private Student student;
- private Gradebook gradebook;
- @Before
- public void setUp() throws Exception {
- gradebook = mock(Gradebook.class);
- student = new Student();
- }
- @Test
- public void calculates_grades_average_for_student() {
- when(gradebook.gradesFor(student)).thenReturn(grades(8, 6, 10)); //stubbing gradebook
- double averageGrades = new GradesService(gradebook).averageGrades(student);
- assertThat(averageGrades).isEqualTo(8.0);
- }
- }
Command Query Separation
僅返回部分結(jié)果而并沒有真實改變系統(tǒng)狀態(tài)的的方法被稱作查詢(Query)。譬如 avarangeGrades,用于返回學(xué)生成績平均值的函數(shù)就是非常典型的例子:Double getAverageGrades(Student student);。該函數(shù)僅返回了某個值,而沒有其他的任何副作用。正如我們上文中介紹的,我們可以使用 Stubs 來替換提供實際成績值的函數(shù),從而簡化了整個測試用例的編寫。不過除了 Query 之外還有另一個類別的方法,被稱作 Command。即當某個函數(shù)在執(zhí)行某些操作的時候還改變了系統(tǒng)狀態(tài),不過該類型函數(shù)往往沒有什么返回值:void sendReminderEmail(Student student);。這種對于方法的劃分方式也就是 Bertrand Meyer 在Object Oriented Software Construction 一書中介紹的 Command Query 分割法。
對于 Query 類型的方法我們會優(yōu)先考慮使用 Stub 來代替方法的返回值,而對于 Command 類型的方法的測試則需要依賴于 Mock。
Mock
- Mocks are objects that register calls they receive. In test assertion we can verify on Mocks that all expected actions were performed.Mocks 代指那些僅記錄它們的調(diào)用信息的對象,在測試斷言中我們需要驗證 Mocks 被進行了符合期望的調(diào)用。
當我們并不希望真的調(diào)用生產(chǎn)環(huán)境下的代碼或者在測試中難于驗證真實代碼執(zhí)行效果的時候,我們會用 Mock 來替代那些真實的對象。典型的例子即是對郵件發(fā)送服務(wù)的測試,我們并不希望每次進行測試的時候都發(fā)送一封郵件,畢竟我們很難去驗證郵件是否真的被發(fā)出了或者被接收了。我們更多地關(guān)注于郵件服務(wù)是否按照我們的預(yù)期在合適的業(yè)務(wù)流中被調(diào)用,其概念如下圖所示:
- public class SecurityCentral {
- private final Window window;
- private final Door door;
- public SecurityCentral(Window window, Door door) {
- this.window = window;
- this.door = door;
- }
- void securityOn() {
- window.close();
- door.close();
- }
- }
在上述代碼中,我們并不想真的去關(guān)門來測試 securityOn 方法,因此我們可以設(shè)置合適的 Mock 對象:
- public class SecurityCentralTest {
- Window windowMock = mock(Window.class);
- Door doorMock = mock(Door.class);
- @Test
- public void enabling_security_locks_windows_and_doors() {
- SecurityCentral securityCentral = new SecurityCentral(windowMock, doorMock);
- securityCentral.securityOn();
- verify(doorMock).close();
- verify(windowMock).close();
- }
- }
在 securityOn 方法執(zhí)行之后,window 與 door 的 Mock 對象已經(jīng)記錄了所有的交互信息,這就允許我們能夠去驗證 Window 與 Door 是否被真實的調(diào)用?;蛟S有人會疑問是否在真實環(huán)境下門與窗是否被真的關(guān)閉了?其實我們并不能保證,不過這也不是我們關(guān)注的點,也不是 SecurityCentral 這個類關(guān)注的目標。門與窗是否能被正常的關(guān)閉應(yīng)該是由 Door 與 Window 這兩個類所關(guān)注的。
【本文是專欄作者“張梓雄 ”的原創(chuàng)文章,如需轉(zhuǎn)載請通過與作者聯(lián)系】
戳這里,看該作者更多好文
網(wǎng)頁名稱:測試中Fakes、Mocks以及Stubs概念明晰
本文鏈接:http://fisionsoft.com.cn/article/coijcid.html


咨詢
建站咨詢
