2011-05-11 70 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