5

给定以下代码

public class Entity
{
    public string Name { get; set; }
    public string Status { get; set; }
}

public interface IRepository
{
    void InsertEntity(Entity entity);
    void UpdateEntity(Entity entity);
}

public class Processor
{
    private IRepository _repository;

    public Processor(IRepository repository)
    {
        _repository = repository;
    }

    public void Execute(string name)
    {
        var entity = new Entity() { Name = name, Status = "Initialized" };
        _repository.InsertEntity(entity);
        // do other things with the entity
        entity.Status = "Processed";
        _repository.UpdateEntity(entity);
    }
}

我可以编写一个单元测试来验证存储库是否在 Execute 方法中被调用,并使用方法 InsertEntity 保存实体的值。换句话说,我想确保在调用 InsertEntity 时,实体的 Status 属性的值是“Initialized”。所以我的单元测试是这样的:

[TestMethod]
public void ShouldSaveEntityWithStatusInitialized()
{
    var mock = new Mock<IRepository>();
    var processor = new Processor(mock.Object);
    processor.Execute("test");
    mock.Verify(m => m.InsertEntity(It.Is<Entity>(e => e.Status == "Initialized")), Times.Once()); // fail
}

但是,即使使用 Status = "Initialized" 调用 InsertEntity 方法,此代码也会失败(我已经调试过了)。我认为这是因为实体对象在 Execute 方法的执行过程中发生了变化(最后状态属性更改为“已处理”),并且 Moq 验证了对更改对象的调用。事实上,这个其他单元测试运行良好。

[TestMethod]
public void ShouldUpdateEntityWithStatusProcessedAtTheEnd()
{
    var mock = new Mock<IRepository>();
    var processor = new Processor(mock.Object);
    processor.Execute("test");
    mock.Verify(m => m.InsertEntity(It.Is<Entity>(e => e.Status == "Processed")), Times.Once());
}

我发现使我的第一个单元测试有效的唯一方法是使用以下解决方法。我使用 Moq 的回调功能保存了 Status 属性的值,然后再断言。

[TestMethod]
public void ShouldSaveEntityWithStatusInitialized_withWorkaround()
{
    var mock = new Mock<IRepository>();
    var processor = new Processor(mock.Object);
    string status = string.Empty;
    mock.Setup(m => m.InsertEntity(It.IsAny<Entity>())).Callback((Entity e) => status = e.Status);
    processor.Execute("test");
    Assert.AreEqual("Initialized", status);
}

但我不喜欢那样。我想知道是否有办法让 Moq在 STU(被测系统)执行期间验证对模拟对象的调用,而不是在所有执行完成之后。

谢谢

4

2 回答 2

2

恕我直言,最后一种方法(您称之为“hack”)是测试状态值的正确方法。对我来说更清楚的是,您要在此测试中验证的是在InsertEntity调用方法时状态设置为“已初始化”。

您想与验证一起使用的方法对于您正在测试的内容更加模糊。您是要确认InsertEntity被调用还是要测试参数是否为“已初始化”,或者两者兼而有之,我不知道。如果您想同时测试两者,那么这实际上是两个不同的单元测试。

您还可以执行以下操作...

mock.Setup(m => m.InsertEntity(It.Is<Entity>(e => e.Status == "Initialized")));

但我也不喜欢这种方法,因为这意味着InsertEntity当状态不是“Initialized”值时,生产代码将运行而不是你的模拟。更有可能单元测试仍然会失败,但是从单元测试返回的断言失败消息更加模糊(失败是由于真实方法内部发生的事情而发生的InsertEntity)。回调准确地说明了您在单元测试中测试的内容。

于 2012-12-19T20:58:53.110 回答
1
[TestMethod]
public void ShouldSaveEntityWithStatusInitialized()
{
    // arrange
    var mock = new Mock<IRepository>();
    mock.Setup(m => m.InsertEntity(It.Is<Entity>(e => e.Status == "Initialized")));
    var processor = new Processor(mock.Object);

    // act
    processor.Execute("test");

    // assert
    mock.Verify();
}
于 2012-12-19T21:23:46.617 回答