22 January 2006

這是老調了.... 不過我們的新人沒碰過這種東東,所以只好又再發信給我們的 team 內部看,有興趣的人可以參考我們的 convention:


一般而言,大多數有按照 TDD (Test Driven Development) 開發的程式,最後Production Code 和 Test Code 的比例大約是 1:1。開發了 50000 行,就會有 50000 行的 Test。所以寫 Test 也需要有技巧的組織,以及運用一些 design pattern ,不然到後來會全部亂成一團。Object Mother 是我們 team 裡面用的最多的一 個 pattern,下面是有關 Object Mother Pattern 的資料:

Object Mother Pattern

這個站也是 Unit Test 的 pattern 的大集合,首頁在:

Patterns of XUnit Test Automation

這些 pattern 都很短很小,希望大家有空多看,讓寫 Test 變的更輕鬆更有效率。

==================如何使用 Object Mother 分隔線===================

Object Mother 是一種 pattern,怎麼運用就看個人發揮。我們 team 裡運用這個 pattern 已經有一段時間了,現在也有了一些 convention,整理如下:

Object Mother 在我們這簡稱 OM,Class 名字通常取做 xxxOM。

一般而言一個 package 裡面有一個 OM

比方說

     org.bioinfo.project         ==>  ProjectOM
     org.bioinfo.user            ==>  UserOM
     org.bioinfo.user.authority  ==>  AuthortyOM

這並沒有硬性規定。你要一個 class 寫一個 OM 也行,不過太多 OM並不是很好管理,請自行拿捏。

OM 只存在在 "test" 的 source folder 裡

換句話說 production code 的 folder "src" 不會有 OM,也不會用到 OM 的 method。如果你不小心 "src" 裡用到了,那麼跑 C.I. (Anthill) 時會 compile 不過,直接拿到一個大紅燈。

OM 都需繼承 BaseObjectMother 這個 class

  • BaseObjectMother 已經和 BaseDAOTestCase, BaseIntegrationMockStrutsTestCase...等等程式整合好
  • 所以你在 OM 可以直接拿到 Spring 的資源,例如 hibernateTemplate,來寫資料庫相關的 Fixture

  • BaseObjectMother 也提供 randomInt() randomStr() 之類的 method方便產生亂數的資料
  • 有興趣的人可以自己去看 BaseObjectMother 這個裡面有什麼東東。如果你發現有一些工具類的 method,常常在很多 OM 裡面會用到,你也可以自己加 method 到 BaseObjectMother 裡。

OM 裡面有兩大類的 method,一個是純物件,另一個是資料庫物件

這兩種分法是我們的 convention,請大家繼續延用。產生純物件類的 method 以 new 為開頭。產生資料庫物件的則以 saved 為開頭

比方說,產生 User 純物件的 method 可如下:

       UserOM.newUser () ;
       UserOM.newUserWithName(String name) ;
       ProjectOM.newProject(String projectCode) ;
       ProjectOM.newProject(String projectCode, User projectOwner) ;
       ...

這些 method 裡面所有涉及到的物件,都不會跟資料庫存取。換句話說,method裡面的物件都是直接 new 產生的,而不是從資料庫取出,或是曾經存到資料庫裡。

而產生資料庫物件則如下:

       UserOM.savedUser() ;
       UserOM.savedUsers(int noOfUsers) ;
       OrderOM.savedOrder(int orderId, Date orderDate) ;
       OrderOM.savedOrder(int orderId, int noOfLineItems) ;
       ...

這一類的寫法完全與純物件 method 相反:它裡面所涉及、所產生的物件全部都要存到資料庫裡。

設計上,純物件類的適合用在一般的測試或是 Struts 的測試上,它的優點是跑起來 快,寫起來也很簡單。另一個資料庫類的,因為跑起來很慢,也不大好寫,所以只適合用在 DAO 或是整合性的測試上,其他地方請勿使用。如果你發現你不是在做 DAO 或是整合性的測試,卻要用到 savedXXX() 的 method,那表示你的 Production Code 或是 Test Code 有地方設計錯了,請重新再思考一次。

OM 的 method 的內容盡可能避免重覆

來看下面 UserOM 的例子:

      public static User newUser() {
          return new User( "myFirstName", "myLastName", "myEmail@com.tw") ;
      }
      public static User newUser(String firstName) {
          return new User( firstName, "myLastName", "myEmail@com.tw") ;
      }

這兩個 method 很簡單,一個是產生一個預設的假 User,另一個則可以指定假User 的firstName。上面這兩個 method 犯了一個錯誤,就是程式碼重覆,應該要改為:

      public static User newUser() {
          return newUser( "myFirstName" ) ;  //重用下面的 method
      }
      public static User newUser(String firstName) {
          return new User( firstName, "myLastName", "myEmail@com.tw") ;
      }

讓上面的第一個 method 去呼叫另一個變化比較多的第二個 method,達到重用。OM 的一個重要精神便是組織 Fixture,所以OM 裡面的method 也必需善加互相使用,避免重覆,這樣花時間寫的 OM 才有意義。

OM 裡面已經寫好的 method,就不能再改內容

比方說你今天已經寫了一個 OM 的 method:

     public static User newUser() {
         return newUser( "myFirstName" ) ;
     }

那麼,大概從明天起,你就不能再改這個 method 的內容為:

     public static User newUser() {
         return newUser( "yourFirstName" ) ; //錯誤示範
     }

原因是這個 method 一旦寫出來,除了你自己之外,其他人也可能開始用了,你改成別的,別人的測試可能就會 fail。一旦遇到這種狀況,請再加寫另一個method:

     public static User newUser() {
         return newUser( "myFirstName" ) ; //原來的不變
     }
     public static User newAnotherUser() {  //多一個新的
         return newUser( "yourFirstName" ) ;
     }

當然你也可以做一個 User newUser(String firstName) 的 method 來解決。

OM 的 method 裡如果有 throw Checked Exception 時,該 method 要改成throw 最大的 Exception

比方說:

      public static Order newOrder(int items) throws OrderException {
          Order order = new Order();
          order.calulate(items) ; //這個 method 會 throw OrderException
          reutrn order ;
      }

你要改成最大的 Exception:

       public static Order newOrder(int items) throws Exception {
           Order order = new Order();
           //......
       }

對 Test 來說 OM 裡面如果發生 OrderException 和 Exception 都是意外,不該在 Test 的過程中發生,如果發生就表示 OM 寫錯了,所以寫成小的 Exception或是大的都沒啥差別。而寫成最大的 Exception 可以讓未來維護測試的程式碼更簡單,所以我們推薦直接 throw 最大的 Exception。

下面這種寫法是錯誤的示範:

       public static Order newOrder(int items)  {
            Order order = new Order();
            try {
               order.calulate(items) ; //這個 method 會 throw OrderException
            } catch (OrderException e) { // 錯誤的示範
               throw new RuntimeException(e) ;
            }
            reutrn order ;
       }

為什麼這種寫法不適合在 OM 裡用?這裡我就不講了,有興趣的人下次遇到這種狀況時,可以自己寫寫看,體會一下為什麼這樣寫不好。

==============================================================

OK,先這樣子,有問題大家在討論。



回響

可以用 Tag <I>、<B>,程式碼請用 <PRE>