我有類似下面的方法:創建一個新的對象單元測試無效方法
public void ExecuteSomeCommand()
{
new MyCommand(someInt, SomeEnum.EnumValue).Execute();
}
我想測試是在傳遞給ICommand的對象我的構造函數的枚舉值創造是正確的價值。有什麼辦法可以用Rhino.Mocks做到這一點?
我有類似下面的方法:創建一個新的對象單元測試無效方法
public void ExecuteSomeCommand()
{
new MyCommand(someInt, SomeEnum.EnumValue).Execute();
}
我想測試是在傳遞給ICommand的對象我的構造函數的枚舉值創造是正確的價值。有什麼辦法可以用Rhino.Mocks做到這一點?
選項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。
沒有我知道的。最接近我想到的是使用工廠,然後創建該工廠的StrictMock
。類似的東西:
readonly ICommandFactory factory;
public Constructor(ICommandFactory factory)
{
this.factory = factory;
}
public void ExecuteSomeCommand()
{
factory.Create(someInt, SomeEnum.EnumValue).Execute();
}
然後,你可以把期望調到Create()
。
HTH
如果你這樣做,你可能需要一個命令工廠每個特定的命令。在OP需要一個'IMyCommandFactory'的情況下。這可能不太理想。依然使用(構造函數)依賴注入+1。 – Steven 2011-05-11 11:07:03
如果枚舉值出現意外,構造函數是否會拋出異常? – MattDavey 2011-05-11 10:37:24
構造函數本身不會拋出。參數由Execute方法傳遞給通過NavigationManager類創建的視圖的視圖模型。新的視圖模型然後使用某些顯示屬性的枚舉,如果它是一個意外的值,它會拋出。 – alimbada 2011-05-11 10:51:01