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メソッドの実行中にエンティティオブジェクトが変更され(最後にStatusプロパティが "Processed"に変更され)、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);
}

しかし、私はそれが好きではありませんでした。すべての実行が完了した後ではなく、STU(テスト対象システム)の実行中にモックオブジェクトに対して行われた呼び出しをMoqに検証させる方法があるかどうかを知りたいです。

ありがとう

4

2 に答える 2

2

IMHO、最後のアプローチ(「ハック」と呼ぶ)は、ステータスの値をテストするための正しいアプローチです。このテストで確認したいのは、InsertEntityメソッドが呼び出されたときにステータスが「初期化済み」に設定されていることです。

検証で使用するアプローチは、テスト対象を正確に把握するために、よりあいまいになります。が呼び出されたことを確認したいのInsertEntityか、パラメータが「初期化」されているのか、あるいはその両方であるのかをテストしたいのか、わかりません。そして、両方をテストしたい場合、これは実際には2つの異なる単体テストになります。

次のこともできます...

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

InsertEntityただし、ステータスが「初期化済み」の値でない場合、モックの代わりに本番コードが実行されることを意味するため、このアプローチも好きではありません。多くの場合、単体テストは引き続き失敗しますが、単体テストから返されるアサーション失敗メッセージはよりわかりにくくなります(実際の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 に答える