2013-08-18 47 views
3

目前我每個TestMethod內被測創建對象寫我的單元測試。這樣做的主要原因是促進自我遏制,便於可讀性/調試,還可能在施工期間調整依賴關係。創建被測對象TestMethod的VS TestInitialize

public class MyClass 
{ 
    public MyClass(ISomeService serviceA, ISomeOtherService serviceB) 
    { 
     ... 
    } 
} 

[TestMethod] 
public void it_should_do_something() 
{ 
    var myClass = new MyClass(serviceA, serviceB); 
    myClass.DoSomething(); 
    ... 
} 

然而,這正在成爲一個維護的噩夢,尤其是當有可能對班裏很多測試,我想其他的依賴添加到構造。它要求我改變我的測試課程中的每一條建設線。

我希望通過在TestInitialize方法中初始化對象來保持乾燥,這將改善維護,同時仍保持自我控制的方面。

private MyClass _myClass; 

[TestInitialize] 
public void Init() 
{ 
    _myClass = new MyClass(serviceA, serviceB); 
} 

唯一的缺點我能看到這樣做的是,我可能需要重新初始化對象,如果我需要一組不同的相關性被注入。對於第一次查看代碼的其他開發人員來說,閱讀/調試可能也不太清楚。這種方法有沒有其他負面因素,我沒有想到?

  1. 此場景的最佳做法是什麼?

  2. 它是不好的做法,初始化TestMethod的外部類?

  3. 我應有利於維護和DRY原則,或者讓事情變得自成一體的可讀性/邏輯的目的呢?

+1

我使用TestInitialize的方式,你給第二。正是出於這個原因。我相信如果您的單元測試課程集中並且只測試'一件事'(沒有蔓延),那麼這種方式可以使測試變得很好並且乾淨,並且'設置'會被排除在外。我不認爲可讀性會受到這種干擾。 – CodeBeard

回答

7

該測試應該是:1)可讀性,2)易於編寫/維護。 「可讀」是什麼意思?這意味着它最大限度地減少了跳過代碼的次數,以瞭解特定代碼行上正在做什麼。

考慮創建測試項目中的工廠。我認爲這是爲測試安排是可讀的最好的方法之一:

public static class MyClassFactory 
{ 
    public static MyClass Create(ISomeService serviceA, ISomeOtherService serviceB) 
    { 
    return new MyClass(serviceA, serviceB); 
    } 

    public static MyClass CreateEmpty() 
    { 
    return new MyClass(); 
    } 

//Just an example, but in general not really recommended. 
//May be acceptable for simple projects, but for large ones 
//remembering what does "Default" mean is an unnecessary burden 
//and it somehow reduces readability. 
    public static MyClass CreateWithDefaultServices()  
    { 
    return new MyClass(/*...*/); 
    } 
    //... 
} 

(第二個選項將在後面解釋)。然後你可以使用這樣的:它

[TestMethod] 
public void it_should_do_something() 
{ 
    //Arrange 
    var myClass = MyClassFactory.Create(serviceA, serviceB); 

    //Act 
    myClass.DoSomething(); 

    //Assert 
    //... 
} 

注意如何提高可讀性。除了serviceAserviceB,我只留下符合你的榜樣,你需要了解的一切就是在這條線上。理想情況下,它看起來像這樣:

[TestMethod] 
public void it_should_do_something() 
{ 
    //Arrange 
    var myClass = MyClassFactory.Create(
     MyServiceFactory.CreateStubWithTemperature(45), 
     MyOtherServiceFactory.CreateStubWithContents("test string")); 

    //Act 
    myClass.DoSomething(); 

    //Assert 
    //... 
} 

這提高了可讀性。請注意,即使DRY的設置已結束,所有工廠方法都很簡單明瞭。沒有必要查看他們的定義 - 他們從一見鍾情清楚。

可以緩解寫作和維護得到進一步提高?

一個很酷的想法是提供您的測試項目中的擴展方法MyClass允許可讀的配置:

public static MyClassTestExtensions 
{ 
    public static MyClass WithService(
     this MyClass @this, 
     ISomeService service) 
    { 
    @this.SomeService = service; //or something like this 
    return @this; 
    } 

    public static MyClass WithOtherService(
     this MyClass @this, 
     ISomeOtherService service) 
    { 
    @this.SomeOtherService = service; //or something like this 
    return @this; 
    } 
} 

那麼你當然像這樣使用:

[TestMethod] 
public void it_should_do_something() 
{ 
    //Arrange 
    var serviceA = MyServiceFactory.CreateStubWithTemperature(45); 
    var serviceB = MyOtherServiceFactory.CreateStubWithContents("test string"); 
    var myClass = MyClassFactory 
     .CreateEmpty() 
     .WithService(serviceA) 
     .WithOtherService(serviceB); 

    //Act 
    myClass.DoSomething(); 

    //Assert 
    //... 
} 

當然中服務不是這種配置的一個好例子(通常,沒有它們的對象是不可用的,所以沒有提供默認的構造函數),但是對於它提供的任何其他屬性一個巨大的優勢:使用N個屬性進行配置,您可以維護一個乾淨可讀的方式,使用N個擴展方法創建所有可能的配置的測試對象,而不是N x N個工廠方法,並且在構造函數或工廠方法調用中沒有N長參數列表。

如果你的類不像上面假設的那麼方便,一種可能的解決方案是對它進行子類化,併爲子類定義工廠和擴展。

如果它沒有意義爲對象與它的屬性中的一部分存在,他們不能設定你能流利地配置一個工廠,然後創建對象:

//serviceA and serviceB creation 
var myClass = MyClassFactory 
    .WithServiceA(serviceA) 
    .WithServiceB(serviceB) 
    .CreateMyClass(); 

我希望這種工廠的代碼很容易推斷,所以我不會寫在這裏,因爲這個答案已經看起來有點長了)

+1

我認爲對於99%的案例,你對一個簡單工廠的建議可以滿足我的需求。在更復雜的結構組合中,我可以看到第二種解決方案的好處。感謝您的詳細回覆。 –