2011-05-11 68 views
8

我有類似下面的方法:創建一個新的對象單元測試無效方法

public void ExecuteSomeCommand() 
{ 
    new MyCommand(someInt, SomeEnum.EnumValue).Execute(); 
} 

我想測試是在傳遞給ICommand的對象我的構造函數的枚舉值創造是正確的價值。有什麼辦法可以用Rhino.Mocks做到這一點?

+0

如果枚舉值出現意外,構造函數是否會拋出異常? – MattDavey 2011-05-11 10:37:24

+0

構造函數本身不會拋出。參數由Execute方法傳遞給通過NavigationManager類創建的視圖的視圖模型。新的視圖模型然後使用某些顯示屬性的枚舉,如果它是一個意外的值,它會拋出。 – alimbada 2011-05-11 10:51:01

回答

11

選項1:使用Seam

最簡單的方法是重構該方法有縫:

public void ExecuteSomeCommand() 
{ 
    this.CreateCommand(someInt, SomeEnum.EnumValue).Execute(); 
} 

// Your seam 
protected virtual ICommand CreateCommand(int someInt, 
    SomeEnum someEnum) 
{ 
    return new MyCommand(someInt, SomeEnum.EnumValue); 
} 

這樣你可以攔截通過擴展這個類來創建'新'操作符。當用手這樣做,可能是這樣的:

public FakeSomeService : SomeService 
{ 
    public int SomeInt; 
    public SomeEnum SomeEnum; 

    protected override Command CreateCommand(int someInt, 
     SomeEnum someEnum) 
    { 
     this.SomeInt = someInt; 
     this.SomeEnum = someEnum; 
     return new FakeCommand(); 
    } 

    private sealed class FakeCommand : Command 
    { 
     public override void Execute() { } 
    } 
} 

這個假類可以在您的測試方法使用。


選項2:單獨的行爲和數據

一種更好的方式是將數據從行爲分離。您的命令同時具有數據(消息)和行爲(處理該消息)。如果允許您在代碼庫中進行這樣的更改:例如通過定義命令和命令處理程序來區分這一點。這裏有一個例子:

// Define an interface for handling commands 
public interface IHandler<TCommand> 
{ 
    void Handle(TCommand command); 
} 

// Define your specific command 
public class MyCommand 
{ 
    public int SomeInt; 
    public SomeEnum SomeEnum; 
} 

// Define your handler for that command 
public class MyCommandHandler : IHandler<MyCommand> 
{ 
    public void Handle(MyCommand command) 
    { 
     // here your old execute logic 
    } 
} 

現在你可以使用依賴注入注入一個處理器連接到要測試的類。這個班的學生將是這樣的:

public class SomeService 
{ 
    private readonly IHandler<MyCommand> handler; 

    // Inject a handler here using constructor injection. 
    public SomeService(IHandler<MyCommand> handler) 
    { 
     this.handler = handler; 
    } 

    public void ExecuteSomeCommand() 
    { 
     this.handler.Handle(new MyCommand 
     { 
      SomeInt = someInt, 
      SomeEnum = someEnum 
     }); 
    } 
} 

既然你現在分開了從行爲數據,這將是很容易的創建一個假命令處理器(或使用犀牛嘲笑創建它)來檢查,如果正確的命令被送到處理程序。手動這看起來像這樣:

public class FakeHandler<TCommand> : IHandler<TCommand> 
{ 
    public TCommand HandledCommand { get; set; } 

    public void Handle(TCommand command) 
    { 
     this.HandledCommand = command; 
    } 
} 

這個假處理程序可以在整個單元測試項目中重複使用。使用這種FakeHandler可能看起來像這樣的測試:

[TestMethod] 
public void SomeTestMethod() 
{ 
    // Arrange 
    int expected = 23; 

    var handler = new FakeHandler<MyCommand>(); 

    var service = new SomeService(handler); 

    // Act 
    service.ExecuteSomeCommand(); 

    // Assert 
    Assert.AreEqual(expected, handler.HandledCommand.SomeInt); 
} 

從行爲中分離數據不僅使您的應用程序更容易測試。它使您的應用程序更易於更改。例如,可以將交叉關注點添加到命令的執行中,而無需更改系統中的任何處理程序。因爲IHandler<T>是一種使用單一方法的接口,所以編寫一個decorator是非常容易的,它可以包裝每個處理程序並添加諸如日誌記錄,審計追蹤,分析,驗證,事務處理,容錯改進等等。您可以閱讀更多關於它在this article

+0

我用你的第一個建議去了。假課不是必要的。我能夠使用Rhino.Mocks的AssertWasCalled(...)來檢查傳遞給CreateCommand(...)方法的正確參數。謝謝! :-) – alimbada 2011-05-11 11:27:00

+0

@Alimbada:謝謝。但不要忘記處理程序。想一想這可以如何簡化您的測試和設計。 – Steven 2011-05-11 11:29:30

2

沒有我知道的。最接近我想到的是使用工廠,然後創建該工廠的StrictMock。類似的東西:

readonly ICommandFactory factory; 

public Constructor(ICommandFactory factory) 
{ 
    this.factory = factory; 
} 
public void ExecuteSomeCommand() 
{ 
    factory.Create(someInt, SomeEnum.EnumValue).Execute(); 
} 

然後,你可以把期望調到Create()

HTH

+0

如果你這樣做,你可能需要一個命令工廠每個特定的命令。在OP需要一個'IMyCommandFactory'的情況下。這可能不太理想。依然使用(構造函數)依賴注入+1。 – Steven 2011-05-11 11:07:03