5

MOQ を使用してリポジトリをテストし、リポジトリの動作を模擬しようとしています。私はMOQの初心者ですので、ご容赦ください。

次の方法を考えます。

public static SubmissionVersion DeleteNote(IRepository repository, SubmissionVersion version, Guid noteId)
{
    Note note = repository.GetById<Note>(noteId);
    version.Notes.Remove(note);
    repository.Save(version);
    repository.Delete(note);
    return repository.GetById<SubmissionVersion>(version.Id);
}

このテストは大丈夫ですか?

[Fact]
public void DeleteNoteV2()
{
    // Arrange
    var note = new Note{ Id = Guid.NewGuid()};

    var subVersion = new Mock<SubmissionVersion>();
    subVersion.Setup(x => x.Notes.Remove(note));

    var repo = new Mock<IRepository>();
    repo.Setup(x => x.GetById<Note>(note.Id)).Returns(note);
    repo.Setup(x => x.GetById<SubmissionVersion>(It.IsAny<Guid?>())).Returns(subVersion.Object);

    // Act
    SubmissionVersion.DeleteNote(repo.Object, subVersion.Object, note.Id.Value);

    // Assert
    repo.Verify(x => x.GetById<Note>(note.Id), Times.Once());
    repo.Verify(x => x.Save(subVersion.Object), Times.Once());
    repo.Verify(x => x.Delete(note), Times.Once());

    subVersion.Verify(x => x.Notes.Remove(It.IsAny<Note>()), Times.Once());
}
4

1 に答える 1

9

あなたのアプローチは良いですが、私はいくつかのことを調整します。以下に示すように、テスト コンポーネントとモック コンポーネントにいくつかの変更を加えました。

単体テスト

ユニットテスト内のテスト中のメソッド (以下を参照) は、質問で定義したメソッドと実際には一致しません。

単体テスト メソッドの呼び出し:

SubmissionVersion.DeleteNote(repo.Object, subVersion.Object, note.Id.Value);

テスト中のメソッド:

public static SubmissionVersion DeleteNote
  (IRepository repository, SubmissionVersion version, Guid noteId)

上記のメソッドは別のクラスの一部であると想定しています。私はそれをNotesHelperと呼びました- リポジトリ呼び出しの理想的な名前ではありませんが、コンパイルを機能させるためのものです。

個人的には、単体テストを 2 つの個別の単体テストに分けます。1 つは必要なメソッドが呼び出されているかどうかを確認するためのもので、もう 1 つはメモがコレクションから削除されているかどうかを確認するためのものです。

また、モック化された SubmissionVersion を作成する代わりに、fakeSubmissionVersion を作成しました。これは、SubmissionVersion 内のルーチンがモック化できない可能性があるためです。version.Notes.Remove(note); を確認するために、状態ベースのテスト (推奨) を行うこともできます。呼ばれました。

[Fact]
public void DeleteNote_Deletion_VerifyExpectedMethodsInvokecOnlyOnce()
{
    // Arrange
    var note = new Note { Id = Guid.NewGuid() };
    var fakeSubmissionVersion = new SubmissionVersion() { Notes = new List<Note>() };
    var repo = new Mock<IRepository>();
    repo.Setup(x => x.GetById<Note>(It.IsAny<Guid>())).Returns(note);

    // Act
    NotesHelper.DeleteNote(repo.Object, fakeSubmissionVersion, note.Id.Value);

    // Assert
    repo.Verify(x => x.GetById<Note>(note.Id), Times.Once());
    repo.Verify(x => x.Save(fakeSubmissionVersion), Times.Once());
    repo.Verify(x => x.Delete(note), Times.Once());
}

上記のテストは、各検証を独自のテストで定義することにより、さらに改善できます。

[Fact]
public void DeleteNote_Deletion_VerifyDeleteMethodCalledOnlyOnce()

このようにして、テストが失敗した場合、期待どおりに呼び出されなかったメソッドを正確に知ることができます。場合によっては、上記のように複数の検証を行うと問題が生じることがあります。たとえば、Save メソッドが呼び出されていない場合、Delete メソッドが呼び出されたかどうかはわかりません。これは、Save メソッドが呼び出されず、テストの実行が終了したときに例外がスローされたためです。

これにより、複数のテストが発生しますが、より読みやすく、保守しやすくなります。もちろん、共通コードをファクトリまたはヘルパー メソッドにリファクタリングできます。

[Fact]
public void DeleteNote_RemoveNotes_ReturnsExpectedNotes()
{
    // Arrange
    var note = new Note { Id = Guid.NewGuid() };
    var fakeSubmissionVersion = new SubmissionVersion() { Notes = new List<Note>() { note } };
    var repo = new Mock<IRepository>();
    repo.Setup(x => x.GetById<Note>(It.IsAny<Guid>())).Returns(note);

    // Act
    NotesHelper.DeleteNote(repo.Object, fakeSubmissionVersion, note.Id.Value);

    // Assert
    Assert.AreEqual(0, fakeSubmissionVersion.Notes.Count);
}

追記: また、説明的なユニット テスト メソッド名を提供すると、ユニット テストの読みやすさが向上します。

于 2013-10-25T11:38:15.970 に答える