2013-10-15 64 views
2

這是一個簡單的域類Grails應用程序:Grails域標準驗證器:我應該測試還是不?

class User { 
    String username 

    static constraints = { 
     username unique: true 
    } 
} 

我的問題是:我應該寫單元測試來檢查用戶名域是唯一的?

@Test 
void cannotCreateMoreThanOneUserWithTheSameUsername() { 
    new User(username: 'john').save() 

    def secondUser = new User(username: 'john') 
    assert !secondUser.validate() 
} 

我懷疑這是因爲:

  • 如果我寫按照TDD的原則用戶類,那麼我應該寫執行約束關閉之前失敗的測試。

  • 另一方面,在域中設置唯一約束是一種數據模型配置,而不是真正的邏輯。而且,保存和驗證方法在框架中實現。

回答

1

我會將您的測試工作集中在可能出錯的領域,而不是試圖獲得100%的覆蓋率。有鑑於此,我避免測試任何簡單的,聲明爲。你沒有理由打破任何測試只是重複聲明。很難看出這將如何避免意外破壞這個功能。

如果您正在編寫處理聲明的底層庫,那麼您應該進行writnig測試。如果沒有,依靠圖書館。當然,如果你不相信圖書館作者能夠做到這一點,那麼你可以編寫測試。這裏有一個測試工作與獎勵的交易。

+0

這是關於SO的這類問題的問題。 OP不想測試約束條件。所以他選擇了讓他對此放心的答案。除了說更好的測試越多,沒有真正的錯誤答案。 – Gregg

+0

@Gregg如果你測試了絕對的一切,我想你會有更少的錯誤。但費用是多少?所以最終它總是會降低到只有OP可以決定的權衡,除非我們能夠準確量化諸如上市時間,聲譽,bug成本等一系列業務級別的結果。 –

+0

@ Gregg的問題是維護測試代碼(我幾乎所有的項目都測試了幾乎所有的約束條件)。問題是關於哲學/想法和最佳實踐。 – promanski

6

在我看來,單元測試CRUD方法是不值得的,因爲Grails開發人員已經完全測試了這些方法。另一方面,單元測試約束非常重要,因爲約束可能在應用程序的生命週期中發生變化,並且您希望確保捕獲這些更改。您永遠不知道可能需要修改哪些業務邏輯來支持所做的更改。我喜歡用斯波克,這和典型的約束測試會是這個樣子:

@TestFor(User) 
class UserSpec extends ConstraintUnitSpec { 

    def setup() { 
    mockForConstraintsTests(User, [new User(username: 'username', emailAddress: '[email protected]')]) 
    } 

    @Unroll("test user all constraints #field is #error") 
    def "test user all constraints"() { 
    when: 
    def obj = new User("$field": val) 

    then: 
    validateConstraints(obj, field, error) 

    where: 
    error  | field     | val 
    'blank' | 'username'   | ' ' 
    'nullable' | 'username'   | null 
    'unique' | 'username'   | 'username' 
    'blank' | 'password'   | ' ' 
    'nullable' | 'password'   | null 
    'maxSize' | 'password'   | getLongString(65) 
    'email' | 'emailAddress'  | getEmail(false) 
    'unique' | 'emailAddress'  | '[email protected]' 
    'blank' | 'firstName'   | ' ' 
    'nullable' | 'firstName'   | null 
    'maxSize' | 'firstName'   | getLongString(51) 
    'blank' | 'lastName'   | ' ' 
    'nullable' | 'lastName'   | null 
    'maxSize' | 'lastName'   | getLongString(151) 
    'nullable' | 'certificationStatus' | null 
    } 
} 

這裏的ConstraintUnitSpec基類:

abstract class ConstraintUnitSpec extends Specification { 

    String getLongString(Integer length) { 
    'a' * length 
    } 

    String getEmail(Boolean valid) { 
    valid ? "[email protected]" : "[email protected]" 
    } 

    String getUrl(Boolean valid) { 
    valid ? "http://www.google.com" : "http:/ww.helloworld.com" 
    } 

    String getCreditCard(Boolean valid) { 
    valid ? "4111111111111111" : "41014" 
    } 

    void validateConstraints(obj, field, error) { 


    def validated = obj.validate() 

    if (error && error != 'valid') { 
     assert !validated 
     assert obj.errors[field] 
     assert error == obj.errors[field] 
    } else { 
     assert !obj.errors[field] 
    } 
    } 
} 

這是一個技術,我從一個博客帖子教訓。但我現在不記得它。我會尋找它,如果我找到它,我會確定並鏈接到它。

+0

感謝您的有趣答覆。我沒有詢問CRUD測試。我問了驗證器設置測試。這樣的測試不是重複執行嗎?我的意思是,如果您忘記更改代碼中的某些內容(讓我們說空白:false),您也可以忘記更新測試。有什麼區別? – promanski

+1

如果使用我描述的方法,並且忘記添加約束,則測試將失敗。如果驗證失敗,上述測試模式實際上會通過。這是關鍵。您正在測試以確保您的域將觸發約束(驗證)錯誤。 – Gregg

+0

@Gregg這是你從中得到的博客嗎? http://www.christianoestreich.com/2012/11/domain-constraints-grails-spock-updated/ –

0

經過一些更多的研究,我想分享同一個用戶類的下一個測試樣本,並最終回答我自己的問題。

@Test 
void usernameIsUnique() { 
    def mock = new ConstraintsMock() 
    User.constraints.delegate = mock 
    User.constraints.call() 
    assert mock.recordedUsernameUniqueConstraint == true 
} 

class ConstraintsMock { 
    Boolean recordedUsernameUniqueConstraint = null 

    def username = { Map m -> 
     recordedUsernameUniqueConstraint = m.unique 
     assert m.unique 
    } 
} 

這是非常天真的測試樣本。這實際上是一種行爲的測試,我認爲是不好。 但它真的不同於問題中的測試樣本嗎?

首先要做的事情是:我們想測試什麼邏輯?約束關閉的真正邏輯是什麼?它只是爲我們想要配置的每個字段調用一個gorm的動態方法,並將配置作爲參數傳遞。所以爲什麼不直接在測試中調用這個閉包呢?我爲什麼要調用保存方法?爲什麼我會稱之爲gorm的驗證方法?從這個角度來看,在單元測試中直接調用約束閉包似乎並不是那麼糟糕的主意。

另一方面,Config.groovy中約束閉包和配置閉包有什麼區別?我們不測試配置,是嗎? 我認爲我們不測試配置,因爲配置測試就像這個配置的複本(重複我們自己)。更重要的是,如果今天有人仍然關心這個指標,那麼這種測試甚至不會增加代碼覆蓋率,因爲第一次運行集成或功能測試應該運行所有域的所有約束條件。

最後一件事:這個測試能夠在現實生活中捕捉到什麼樣的錯誤?

總結:在我看來設置簡單約束如「空白」,「可空」或唯一與應用程序配置非常相似。 我們不應該測試代碼的這一部分,因爲如果這樣的測試不僅僅是我們的約束定義的複製副本,它還可以僅僅檢查框架的邏輯。

我爲約束寫了許多單元測試。現在,我在重構過程中將它們移除。我將只留下我自己驗證器邏輯的單元測試。

相關問題