【问题标题】:Reusable Setup for Unit Tests单元测试的可重用设置
【发布时间】:2013-10-25 20:21:01
【问题描述】:

我正在使用 xUnit 和 Moq 来编写我的单元测试,并且我在各种测试中有很多重复的代码,我想以一些可重用的方式提取出来。

重复代码

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);

鉴于以下测试,我该如何清理它们以减少重复?

[Fact]
public void Should_CallRepoGetNoteByIdOnce()
{
    // 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());
}

[Fact]
public void Should_CallSubmissionVerionNotesRemoveOnce()
{
    // 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
    subVersion.Verify(x => x.Notes.Remove(note), Times.Once());
}

[Fact]
public void Should_CallRepoSaveSubmissionVersionOnce()
{
    // 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.Save(subVersion.Object), Times.Once());
}

[Fact]
public void Should_CallRepoDeleteNoteOnce()
{
    // 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.Delete(note), Times.Once());
}

[Fact]
public void Should_CallRepoGetSubmissionVersionByIdOnce()
{
    // 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<SubmissionVersion>(subVersion.Object.Id), Times.Once());
}

[Fact]
public void Should_RemoveNotesFromSubmissionVersion()
{
    // Arrange
    var repo = new CompositeRepository().GenerateCompositeRepository<Guid?>(typeof(SubmissionVersion), typeof(Note));
    var subVersion = new SubmissionVersion { Id = Guid.NewGuid() };
    var note = new Note { Id = Guid.NewGuid(), Content = "Test Note" };

    repo.Save(note);
    subVersion.Notes.Add(note);

    // Act
    subVersion.Notes.ToList().ForEach(x => SubmissionVersion.DeleteNote(repo, subVersion, x.Id.Value));

    // Assert
    Assert.Null(repo.GetById<Note>(note.Id));
}

任何最佳实践的建议/模式?

【问题讨论】:

    标签: c# unit-testing xunit xunit.net


    【解决方案1】:

    我通常通过创建一个单元测试上下文对象来解决这个问题,该对象将可重用的 Mocks 公开为公共属性。该对象将在内部设置公共模拟并将它们公开为公共属性。您可以在此类中定义许多可重用的模拟。

    例如:

    public class UnitTestContext
    {
    
       public Mock<IRepository> Repo {get;set;}
    
    
       public UnitTestContext()
       {
          // create suitable note / subversion objects 
          // either by passing them in or new-ing them up directly with default values. 
          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);
    
       }
    }
    

    然后测试可以创建一个实例:

    [Fact]
    public void Some_Test_In_Need_Of_A_Mocked_Repository()
    {
       var ctx = new UnitTestContext();
    
       SubmissionVersion.DeleteNote(ctx.Repo.Object, subVersion.Object, note.Id.Value);
    }
    

    与将模拟定义为测试类中的成员相比,我更喜欢这种方法,因为 UnitTestContext 可以跨测试类重用。

    如果您在返回值方面需要更大的灵活性,您还可以在构造模拟时将对象传递给上下文。您还可以通过 Repo 属性添加到类之外的模拟。

    【讨论】:

      【解决方案2】:

      一般来说,xUnit 的人不喜欢测试设置的想法。但是如果需要,可以将一些常用的对象暴露为测试类的私有成员,并使用无参数的ctor来初始化。阅读attribute comparisons with other testing frameworks

      private readonly Mock<IRepository> _testRepository;
      private readonly Mock<SubmissionVersion> _submissionVersion;
      private readonly Note _testNote;
      
      public MyTestClass()
      {
         _submissionVersion = new Mock<SubmissionVersion>();
         _testNote = new Note { Id = Guid.NewGuid() };
         _testRepository = new Mock<IRepository>();
         _testRepository.Setup(r => r.GetById<Note>(_testNote.Id).Returns(_testNote);
         _testRepository.Setup(r => r.GetById<SubmissionVersion>(It.IsAny<Guid?>())).Returns(_submissionVersion.Object);
      }
      
      [Fact]
      public void Should_CallSubmissionVerionNotesRemoveOnce()
      {
          // Arrange
          // done is setup
      
          // Act
          SubmissionVersion.DeleteNote(_testRepository.Object, _submissionVersion.Object, note.Id.Value);
      
          // Assert
          _submissionVersion.Verify(x => x.Notes.Remove(note), Times.Once());
      }
      
      [Fact]
      public void Should_CallRepoSaveSubmissionVersionOnce()
      {
          // Arrange
          // in setup
      
          // Act
          SubmissionVersion.DeleteNote(repo.Object, subVersion.Object, note.Id.Value);
      
          // Assert
          _testRepository.Verify(r => r.Save(_submissionVersion.Object), Times.Once());
      }
      

      【讨论】:

        猜你喜欢
        • 2016-03-29
        • 1970-01-01
        • 1970-01-01
        • 2011-01-29
        • 2017-08-31
        • 2016-08-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多