单元测试模拟框架是否会以您的编程方式强迫您?

Do unit test mocking frameworks force you in the way you're programming?

我正在使用 Moq 框架设置单元测试。

我有一个描述 class 的界面,如下所示:

public interface ISourceFileLocation : IFileLocation, IDisposable
{
    bool RemoveAfterTransfer { get; set; }
    void RemoveSource();
    //.....
}

为了确保调用 RemoveSource-Method,我实现了一个基础 class,其中在 dispose 方法中调用了 remove。

public abstract class SourceFileBase : ISourceFileLocation
{
    //......

    public bool RemoveAfterTransfer { get; set; }

    public void RemoveSource()
    {
        if (File.Exists(Uri.AbsolutePath))
        {
            File.Delete(Uri.AbsolutePath);
        }
    }

    public void Dispose()
    {
        if (this.RemoveAfterTransfer)
            this.RemoveSource();
    }
}

模拟 ISourceFileLocation 时没有默认实现,因此为了测试,我想在基础class 上实现测试,其中具体 class 应该继承。

模拟基础时class 我的测试期望 RemoveSource-Method 是虚拟的,这打破了我确保调用该方法的想法!!

这是缺少框架,是否有更好的框架或方法来测试它,或者这是我的代码的问题,我应该重新考虑我的设计吗?

亲切的问候。

测试方法:

    [TestCategory("Source")]
    [TestMethod]
    public void CheckIfRemoveSourceMethodIsCalled()
    {
            //ARRANGE
            var mockSourceLocation = new Mock<SourceFileBase>();
            mockSourceLocation.Object.RemoveAfterTransfer = true;
            mockSourceLocation.Setup(x => x.RemoveSource());
            //ACT
            mockSourceLocation.Object.Dispose();
            /ASSERT
            mockSourceLocation.VerifyAll();
    }

    [TestCategory("Source")]
    [TestMethod]
    public void CheckIfRemoveSourceMethodIsNotCalled()
    {
            //ARRANGE
            var mockSourceLocation = new Mock<SourceFileBase>();
            mockSourceLocation.Object.RemoveAfterTransfer = false;
            //ACT
            mockSourceLocation.Object.Dispose();
            //ASSERT
            mockSourceLocation.Verify(x=>x.RemoveSource(), Times.Never);
    }

我认为您可能在这个示例中误用了 Moq,因为您正在为被测系统 (SUT) 使用相同的模拟对象,并执行您的行为验证。您的测试实质上是检查 SourceFileBase 是否调用自身。通常,您会使用 Moq 来验证对 SUT 的 dependencies 的调用。

SourceFileBase 中的代码对单元测试不友好,因为它直接在 File class 上使用静态方法。为了使其可单元测试,您需要以某种方式对其进行抽象(例如,参见 this question)。

public abstract class SourceFileBase : ISourceFileLocation
{
    private readonly IFileSystem _fileSystem;

    public SourceFileBase(IFileSystem fileSystem)
    {
        _fileSystem = fileSystem;
    }

    ...

    public void RemoveSource()
    {
        _fileSystem.DeleteFile(Uri.AbsolutePath);
    }

    public void Dispose()
    {
        RemoveSource();
    }
}

这样做可以让您测试 Dispose()RemoveSource() 是否都删除了文件。

// Arrange
var mockFileSystem = new Mock<IFileSystem>();
var sut = new Mock<SourceFileBase>(mockFileSystem.Object);
sut.RemoveAfterTransfer = true;
sut.Uri = myTestUri;

// Act
sut.Dispose();

// Assert
mockFileSystem.Verify(f => f.DeleteFile(myTestUri.AbsolutePath));