创建一个新的对象单元测试无效方法
我有类似下面的方法:创建一个新的对象单元测试无效方法
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