第五次重構我們引入了數據庫的設計,用戶信息要從數據庫中讀取,問候語庫存儲在數據庫中,并支持添加與更新。數據庫的引入使自動化測試變得困難了,因為數據狀態總是變化著的,而這種變化使得測試過程不能復現,這是我們不愿看到的。因此,我們在設計時將業務與數據庫訪問分離,形成了UserDao與GreetingRuleDao。此時,我們的設計應當遵從“依賴反轉”原則,即將UserDao與GreetingRuleDao設計成接口,并編寫它們的實現UserDaoImpl與GreetingRuleDaoImpl。這樣設計就為我們Mock掉UserDao與GreetingRuleDao的實現類創造了條件。
這是我們的設計:
為此,我們編寫了這樣的測試程序:
這段測試程序比較長,但其它部分都是打醬油的,核心是那個testSayHelloInTheMorning()用例,即問候早上好這個用例。userDao與greetingRuleDao是兩個接口,我們在實例化它們的時候,并沒有去創建它們的實現類,而是用Mock的方式進行創建:
隨后我們開始定義它們的行為loadUser()與getAllGreetingRules()。在這個測試用例中,我們并不關心它們是怎樣去數據庫里查詢數據并返回的,我們只關心它們是否得到應該得到的參數,并要求它們按照規定返回一個結果:
我們希望被測程序在執行的時候調用了userDao.loadUser(userId),并且調用時傳入的參數userId = 2013090701。如果測試過程中傳入的參數是這個值,這一項檢查點可以通過,否則不能通過。隨后我們希望該函數返回“鮑曉妹”這個用戶對象。通過Mock程序,我們完全將數據庫訪問的過程剝離在自動化測試之外,而只是驗證它的輸入參數,并指定測試所需的返回結果。也就是說數據訪問過程被Mock掉,而大大降低了測試難度。
如果UserDao與GreetingRuleDao的Mock程序不能得到規定的參數時,測試就不能通過,這就是說傳遞給Mock程序的參數也成為了測試程序要驗證的一個輸出。隨后,Mock程序返回規定值,該規定值則成為了被測程序的一個輸入。最后,被測程序根據這個輸入返回結果,為測試程序所驗證,測試結束(如圖4.4所示)。
圖中的BUS層才是我們大量編碼,應當自動化測試的部分。既然是測試就是驗證怎樣的輸入,應當得到怎樣的輸出。Web層向BUS層發出的請求,即調用BUS層某個類的方法,就是測試用例中的一個輸入,執行完該方法后的返回值就是測試用例的一個輸出。但是,這對輸入輸出并不是該測試用例的全部。這里經過BUS層處理以后,經過一系列的邏輯判斷和數據操作,隨后會去調用DAO層進行數據訪問操作。調用DAO層時所傳遞的參數,就是測試用例的另一個輸出。圖中,從DAO層的輸入,到它的輸出,這段數據庫訪問的過程被Mock程序Mock掉了,因為它不在這個用例的測試范圍。然后DAO層返回給BUS層一個結果,該結果就是測試用例的另一個輸入。接著BUS層會再次對這個返回結果進行處理,最后返回給Web層最終的結果。這就是采用Mock方式進行自動化測試的一個完整流程。
采用自動化測試,測試程序將不再驗證后臺的數據庫是否正確,同時也不再驗證前臺的Web應用及其前端設備是否正確。在該例中,系統的真正目的是要在前臺顯示對用戶的問候,因此將會有一個Action或Servlet調用HelloWorld:
然而,這些程序都不適合自動化測試而應采用手工測試。回顧HelloWorld自動化測試建立的過程我們不難發現,它在設計之初就實現了業務邏輯與Web應用、與數據訪問的分離,所以它可以輕易的建立自動化測試。但是,不幸的是,我們大多數的遺留系統在設計之初都沒有考慮到這些。因此,我說,在重構之初首先建立自動化測試機制是不現實的,我們只能采用手工測試結合QTP的方式。只有當我們通過重構,使系統架構滿足自動化測試的條件之后,自動化測試才可以開展。
毫無疑問,測試與重構形成了一個“雞生蛋,還是蛋生雞”的怪圈,成為我們實踐系統重構一大攔路虎。本書將在后面詳細討論這個話題(詳見 第十六章 測試的困境),為你破解這個謎團。
大話重構連載首頁: http://fangang.iteye.com/blog/2081995
特別說明:希望網友們在轉載本文時,應當注明作者或出處,以示對作者的尊重,謝謝!
這是我們的設計:
圖4.3 HelloWorld的設計圖
為此,我們編寫了這樣的測試程序:
private HelloWorld helloWorld = null; private GreetingToUserImpl greetingToUser = null; private GreetingAboutTimeImpl greetingAboutTime = null; private final static List<GreetingRule> GREETING_RULES = getRules(); /** * @throws java.lang.Exception */ @Before public void setUp() throws Exception { helloWorld = new HelloWorld(); greetingToUser = new GreetingToUserImpl(); greetingAboutTime = new GreetingAboutTimeImpl(); helloWorld.setGreetingToUser(greetingToUser); helloWorld.setGreetingAboutTime(greetingAboutTime); } /** * @throws java.lang.Exception */ @After public void tearDown() throws Exception { helloWorld = null; greetingToUser = null; greetingAboutTime = null; } /** * Test method for {@link org...HelloWorld#sayHello(java.util.Date, java.lang.String)}. */ @Test public void testSayHelloInTheMorning() { final Date now = DateUtil.createDate(2013, 9, 7, 9, 23, 11); final long userId = 2013090701; UserDao userDao = createMock(UserDao.class); GreetingRuleDao greetingRuleDao = createMock(GreetingRuleDao.class); expect(userDao.loadUser(userId)).andAnswer(new IAnswer<User>(){ @Override public User answer() throws Throwable { User user = new User(); user.setUserId(userId); user.setName("鮑曉妹"); return user; }}); expect(greetingRuleDao.findAllGreetingRules()) .andAnswer(new IAnswer<List<GreetingRule>>(){ @Override public List<GreetingRule> answer() throws Throwable { return GREETING_RULES; }}); replay(userDao); replay(greetingRuleDao); greetingToUser.setUserDao(userDao); greetingAboutTime.setGreetingRuleDao(greetingRuleDao); String result = helloWorld.sayHello(now, userId); Assert.assertEquals("Hi, 鮑曉妹. Good morning!", result); verify(userDao); verify(greetingRuleDao); }
這段測試程序比較長,但其它部分都是打醬油的,核心是那個testSayHelloInTheMorning()用例,即問候早上好這個用例。userDao與greetingRuleDao是兩個接口,我們在實例化它們的時候,并沒有去創建它們的實現類,而是用Mock的方式進行創建:
UserDao userDao = createMock(UserDao.class); GreetingRuleDao greetingRuleDao = createMock(GreetingRuleDao.class);
隨后我們開始定義它們的行為loadUser()與getAllGreetingRules()。在這個測試用例中,我們并不關心它們是怎樣去數據庫里查詢數據并返回的,我們只關心它們是否得到應該得到的參數,并要求它們按照規定返回一個結果:
final long userId = 2013090701; expect(userDao.loadUser(userId)).andAnswer(new IAnswer<User>(){ @Override public User answer() throws Throwable { User user = new User(); user.setUserId(userId); user.setName("鮑曉妹"); return user; }});
我們希望被測程序在執行的時候調用了userDao.loadUser(userId),并且調用時傳入的參數userId = 2013090701。如果測試過程中傳入的參數是這個值,這一項檢查點可以通過,否則不能通過。隨后我們希望該函數返回“鮑曉妹”這個用戶對象。通過Mock程序,我們完全將數據庫訪問的過程剝離在自動化測試之外,而只是驗證它的輸入參數,并指定測試所需的返回結果。也就是說數據訪問過程被Mock掉,而大大降低了測試難度。
如果UserDao與GreetingRuleDao的Mock程序不能得到規定的參數時,測試就不能通過,這就是說傳遞給Mock程序的參數也成為了測試程序要驗證的一個輸出。隨后,Mock程序返回規定值,該規定值則成為了被測程序的一個輸入。最后,被測程序根據這個輸入返回結果,為測試程序所驗證,測試結束(如圖4.4所示)。
圖4.4 HelloWorld的自動化測試
圖中的BUS層才是我們大量編碼,應當自動化測試的部分。既然是測試就是驗證怎樣的輸入,應當得到怎樣的輸出。Web層向BUS層發出的請求,即調用BUS層某個類的方法,就是測試用例中的一個輸入,執行完該方法后的返回值就是測試用例的一個輸出。但是,這對輸入輸出并不是該測試用例的全部。這里經過BUS層處理以后,經過一系列的邏輯判斷和數據操作,隨后會去調用DAO層進行數據訪問操作。調用DAO層時所傳遞的參數,就是測試用例的另一個輸出。圖中,從DAO層的輸入,到它的輸出,這段數據庫訪問的過程被Mock程序Mock掉了,因為它不在這個用例的測試范圍。然后DAO層返回給BUS層一個結果,該結果就是測試用例的另一個輸入。接著BUS層會再次對這個返回結果進行處理,最后返回給Web層最終的結果。這就是采用Mock方式進行自動化測試的一個完整流程。
采用自動化測試,測試程序將不再驗證后臺的數據庫是否正確,同時也不再驗證前臺的Web應用及其前端設備是否正確。在該例中,系統的真正目的是要在前臺顯示對用戶的問候,因此將會有一個Action或Servlet調用HelloWorld:
Date now = DateUtil.getNow(); String user = SessionUtil.getCurrentUser(session); HelloWorld helloWorld = new HelloWorld(); String greeting = helloWorld.sayHello(now, user); request.setAttribute(“greeting”, greeting);
然而,這些程序都不適合自動化測試而應采用手工測試。回顧HelloWorld自動化測試建立的過程我們不難發現,它在設計之初就實現了業務邏輯與Web應用、與數據訪問的分離,所以它可以輕易的建立自動化測試。但是,不幸的是,我們大多數的遺留系統在設計之初都沒有考慮到這些。因此,我說,在重構之初首先建立自動化測試機制是不現實的,我們只能采用手工測試結合QTP的方式。只有當我們通過重構,使系統架構滿足自動化測試的條件之后,自動化測試才可以開展。
毫無疑問,測試與重構形成了一個“雞生蛋,還是蛋生雞”的怪圈,成為我們實踐系統重構一大攔路虎。本書將在后面詳細討論這個話題(詳見 第十六章 測試的困境),為你破解這個謎團。
大話重構連載首頁: http://fangang.iteye.com/blog/2081995
特別說明:希望網友們在轉載本文時,應當注明作者或出處,以示對作者的尊重,謝謝!
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
