2012-02-20 48 views
7

我正在用C#編寫一系列集合類,每個集合類都實現類似的自定義接口。是否可以爲接口編寫單個單元測試集合,並自動在幾個不同的實現上運行它們?我想避免每個實現的任何重複的測試代碼。我可以實現一系列可重用測試來測試接口的實現嗎?

我願意查看任何框架(NUnit等)或Visual Studio擴展來完成此操作。


對於那些希望做同樣的,我貼我的具體的解決方案,基於斷avandeursen's accepted solution,爲an answer

+1

添加了[lsp](http://stackoverflow.com/questions/tagged/lsp)作爲標記,因爲問題和答案適用於遵循LSP的任何類層次結構。 – avandeursen 2012-02-21 06:52:45

回答

6

是的,那是可能的。訣竅是讓您的單元類測試層次結構遵循代碼的類層次結構。

我們假設您有一個接口Itf,其中實現類C1C2

您首先要爲ItfItfTest)創建一個測試類。要實際執行測試,您需要創建Itf接口的模擬實現。

ItfTest中的所有測試應該通過Itf(!)的任何實施。如果沒有,您的實現不符合

因此Liskov Substitution Principle(在馬丁的SOLID原則OO設計的「L」),來創建一個測試用例C1,您C1Test類可以擴展ItfTest。您的擴展應該用創建C1對象(將其注入或使用GoF factory method)替換模擬對象創建。通過這種方式,所有ItfTest個案都適用於C1類型的實例。此外,您的C1Test類可以包含特定於C1的其他測試用例。

同樣對於C2。你可以重複使用更深的嵌套類和接口。

參考文獻:Binder's Polymorphic Server Test pattern, and McGregor's PACT - 組件測試的並行體系結構。

+1

爲了避免模擬實現,我只是將我的'ItfTest'類聲明爲抽象,並聲明瞭一個'protected abstract Itf CreateInstance();'函數存根。 (請注意'ItfTest'和'C1Test'都需要'[TestClass]'屬性。) – dlras2 2012-02-20 22:37:28

+1

是的,我也使用了工廠方法,因爲它更簡單。我在JUnit中使用了這種測試方法,其中沒有'[TestClass]'屬性,但超類中的'@ Test'註釋被繼承,導致超類測試用例在子類中重新運行。並且:感謝編輯。 – avandeursen 2012-02-21 06:47:40

2

您可以使用MBUnit中的[RowTest]屬性來執行此操作。下面,其中傳遞的方法的字符串,以指示要實例,然後該接口的實現類示出了例如經由反射創建此類:

[RowTest] 
[Row("Class1")] 
[Row("Class2")] 
[Row("Class3")] 
public void TestMethod(string type) 
{ 
    IMyInterface foo = Activator.CreateInstance(Type.GetType(type)) as IMyInterface; 

    //Do tests on foo: 
} 

在[行]屬性,可以通過任意數量的的輸入參數,例如測試的輸入值或方法調用返回的預期值。您將需要添加相應類型的參數作爲測試方法輸入參數。

+0

你能編輯你的答案來解釋我將如何測試實現這種方式?你似乎在說每個單元測試都需要一個適用於所有實現的switch語句,這不是我想要的。 – dlras2 2012-02-20 20:21:12

+0

如果您不想使用switch語句,則不需要使用switch語句。我提到你也可以使用反射。看到安東尼的答案爲例。我還更新了我的帖子,以包含使用反射來實例化接口的各種具體實現的示例。 – 2012-02-20 20:51:06

+0

我可能會寫這個來接受'IMyInterface'的實例,而不僅僅是名字,但仍然是+1。 – dlras2 2012-02-20 22:34:49

2

Joe's答案上展開,您可以使用類似MBUnit的RowTest的方式在NUnit中使用[TestCaseSource]屬性。你可以在那裏創建一個帶有你的類名的測試用例源代碼。然後,您可以修飾TestCaseSource屬性的每個測試。然後使用Activator.CreateInstance你可以轉換到界面,你會被設置。

像這樣的東西(注 - 頭編譯)

string[] MyClassNameList = { "Class1", "Class2" }; 

[TestCaseSource(MyClassNameList)] 
public void Test1(string className) 
{ 
    var instance = Activator.CreateInstance(Type.FromName(className)) as IMyInterface; 

    ... 
} 
1

這是根據我的具體實施關閉的avandeursen's answer

[TestClass] 
public abstract class IMyInterfaceTests 
{ 
    protected abstract IMyInterface CreateInstance(); 

    [TestMethod] 
    public void SomeTest() 
    { 
     IMyInterface instance = CreateInstance(); 
     // Run the test 
    } 
} 

每個接口的實現則定義了以下測試類:

[TestClass] 
public class MyImplementationTests : IMyInterfaceTests 
{ 
    protected override IMyInterface CreateInstance() 
    { 
     return new MyImplementation(); 
    } 
} 

SomeTest運行一次每個具體TestClass來自IMyInterfaceTests。通過使用抽象基類,我可以避免使用任何模擬實現。一定要將TestClassAttribute添加到這兩個班,否則這將無法工作。最後,如果需要,您可以將任何特定於實現的測試(如構造函數)添加到子類。

+0

你使用特定的測試框架嗎?我正在使用VS2012中包含的單元測試,並且從另一個「共享」測試項目(名稱空間)繼承的測試不會被拾取。 – drzaus 2013-04-30 17:06:43

+0

奇怪 - 在同一個項目中繼承的測試,不同的命名空間顯示正常。只有當他們在一個不同的項目中,他們沒有註冊。我錯過了什麼? – drzaus 2013-04-30 19:50:53