2011-02-17 39 views
2

很多時候我參與API的設計/實現,我面臨着這樣的困境。編寫/實現API:可測試性與信息隱藏

我的information hiding一個非常強有力的支持者,並嘗試使用各種技術的是,包括但不限於內部類,私有方法,包私人預選賽等

的問題,這些技術是他們傾向於防止良好的可測試性。雖然其中一些技巧可以解決(例如通過將課程放入同一個包中來實現包的私密性),但其他技術可能需要反思魔術或其他技巧。

讓我們看看具體的例子:

public class Foo { 
    SomeType attr1; 
    SomeType attr2; 
    SomeType attr3; 

    public void someMethod() { 
     // calculate x, y and z 
     SomethingThatExpectsMyInterface something = ...; 
     something.submit(new InnerFoo(x, y, z)); 
    } 

    private class InnerFoo implements MyInterface { 
     private final SomeType arg1; 
     private final SomeType arg2; 
     private final SomeType arg3; 

     InnerFoo(SomeType arg1, SomeType arg2, SomeType arg3) { 
     this.arg1 = arg1; 
     this.arg2 = arg2; 
     this.arg3 = arg3; 
     } 

     @Override 
     private void methodOfMyInterface() { 
     //has access to attr1, attr2, attr3, arg1, arg2, arg3 
     } 
    } 
} 

有充足的理由不暴露InnerFoo - 沒有其他類,庫應該可以訪問它,因爲它並沒有定義任何公共合同和筆者特意沒」 t希望它是可訪問的。然而要實現100%TDD-猶太和訪問沒有任何反映技巧,InnerFoo需要重構這樣的:

private class OuterFoo implements MyInterface { 
    private final SomeType arg1; 
    private final SomeType arg2; 
    private final SomeType arg3; 
    private final SomeType attr1; 
    private final SomeType attr2; 
    private final SomeType attr3; 

    OuterFoo(SomeType arg1, SomeType arg2, SomeType arg3, SomeType attr1, SomeType attr2, SomeType attr3) { 
     this.arg1 = arg1; 
     this.arg2 = arg2; 
     this.arg3 = arg3; 
     this.attr1 = attr1; 
     this.attr2 = attr2; 
     this.attr3 = attr3; 
    } 

    @Override 
    private void methodOfMyInterface() { 
     //can be unit tested without reflection magic 
    } 
} 

這examply只涉及3個ATTRS,但它是相當合理的,有5-6和OuterFoo構造函數將不得不接受8-10個參數!在頂部添加getters,並且您已經有100行完全無用的代碼(獲取這些attrs以進行測試也需要getter)。是的,我可以通過提供構建器模式來改善情況,但我認爲這不僅是過度工程,而且也會挫敗TDD本身的目的!

此問題的另一個解決方案是公開類Foo的受保護方法,將其擴展到FooTest並獲取所需的數據。再次,我認爲這也是一個不好的方法,因爲protected方法的確定義合同並通過公開它我現在已經隱式簽署它。

不要誤解我的意思。 我喜歡寫可測試代碼我喜歡簡潔,乾淨的API,短代碼塊,可讀性等。但是我不喜歡的是在信息隱藏方面做出任何犧牲只是因爲單元測試更容易

任何人都可以提供任何想法(通常,特別是)?給出的例子還有其他更好的解決方案嗎?

+1

你基本上提出了一個討論話題,而不是問一個可以回答的具體問題。至少對於你提出更好的方法來編寫特定代碼的部分,你最好在http://codereview.stackexchange.com上提問。 – 2011-02-17 16:55:47

+0

如果您認爲這不僅僅是一個問題而是一個討論主題,請隨意將其轉換爲社區wiki。至於codereview ..它只有229個問題在整個網站和出席是糟糕的。我不知道人們不允許在SO問題上發佈代碼......而且,這更多的是僞代碼而不是具體的代碼。 – mindas 2011-02-17 16:59:12

+2

不知道你爲什麼會努力避免反思。它有它自己的缺點,但是如果它允許你維護你想要的安全模型而不需要僞代碼,這可能是一件好事。 – 2011-02-17 17:17:55

回答

3

我認爲你應該重新考慮使用反射。

它有它自己的缺點,但如果它允許你維護你想要的安全模型而不需要僞代碼,那可能是件好事。反思往往不是必需的,但有時候沒有好的替代品。

信息隱藏的另一種方法是將類/對象視爲黑盒子,不能訪問任何非公共方法(雖然這可以允許測試通過「錯誤」的原因,即答案是正確的,但對於錯誤原因)

4

我對這種類型的東西的答案是「測試代理」。在你的測試包中,從被測系統派生一個子類,它包含受保護數據的「傳遞」訪問器。

優點:

  • 您可以直接測試,或模擬,方法你不想公之於衆。
  • 由於測試代理位於測試包中,因此您可以確保它永遠不會用於生產代碼。
  • 與直接測試類相比,測試代理需要對代碼進行的更少更改才能使其可測試。

缺點:

  • 類必須是繼承(沒有final
  • 你需要訪問不能是私有的任何隱藏的成員;保護是你能做的最好的。
  • 這不是嚴格的TDD; TDD將首先應用於不需要測試代理的模式。
  • 這不是嚴格的單元測試,因爲在某些級別上,您依賴代理和實際SUT之間的「集成」。

總之,這通常應該是一個罕見的。我傾向於僅將它用於UI元素,其中最佳實踐(以及許多IDE的默認行爲)是將嵌套的UI控件聲明爲從類外部不可訪問。絕對是一個好主意,因此您可以控制調用者如何從UI獲取數據,但這也使得難以給控件一些已知值來測試該邏輯。

2

我不明白信息隱藏是如何在抽象中降低可測試性的。

如果你注入在這種方法中使用的SomethingThatExpectsMyInterface,而不是直接將其構建:在單元測試

public void someMethod() { 
    // calculate x, y and z 
    SomethingThatExpectsMyInterface something = ...; 
    something.submit(new InnerFoo(x, y, z)); 
} 

然後,你可以注入這個類的SomethingThatExpectsMyInterface一個模擬版本,輕鬆地斷言,當你發生了什麼用不同的輸入調用someMethod() - mockSomething接收某些值的參數。

無論如何,我認爲你可能已經過分簡化了這個例子,因爲如果SomethingThatExpectsMyInterface接收到它的類型的參數,InnerFoo不能是私有類。

「信息隱藏」並不一定意味着您在類之間傳遞的對象需要保密 - 只需要您不需要使用此類的外部代碼即可瞭解InnerFoo或其他的詳細信息這個課程如何與他人溝通的細節。

2

SomethingThatExpectsMyInterface可以在Foo以外的地方測試吧?你可以用你自己的測試類來調用它的submit()方法,它實現了MyInterface。所以該單位照顧。現在你正在測試Foo.someMethod()與那個經過良好測試的單位和你未經測試的內部類。這並不理想 - 但並不算糟糕。當你試駕someMethod()時,你隱式地測試駕駛內部課堂。我知道這不是純粹的 TDD,按照一些嚴格的標準,但我會認爲它足夠。除了滿足失敗的測試外,你不會寫內部類的一行;在測試和測試代碼之間存在單一級別的間接並不構成大問題。

-1

什麼,我不喜歡當談到信息隱藏

首先,與Python工作了幾年被作出任何犧牲。

private不是特別有用。它使課堂的延伸變得困難,並且使測試變得困難。

考慮重新思考你對「隱藏」的立場。

0

在你的例子中,它看起來像Foo類真的需要一個協作者 InnerFoo。

在我看來,信息隱藏和可測試性之間的緊張關係可以通過「composite simpler than the sum of its parts」的座右銘來解決。

Foo是一個複合體的外觀(就你的情況而言只是InnerFoo,但沒關係)。應該根據其預期行爲測試外觀對象。如果您覺得InnerFoo對象代碼沒有被Foo行爲的測試所驅動,那麼您應該考慮InnerFoo在您的設計中代表什麼。這可能是你錯過了一個設計理念。當你找到它,命名它並定義它的職責時,你可以單獨測試它的行爲。