繁体   English   中英

以接口为参数的void方法的单元测试

[英]Unit test for void method with Interface as parameter

单元测试的新手,我有下面的示例代码,我想为此创建一个单元测试,请建议我应该怎么做才能为此创建一个单元测试? 任何链接或指针都有助于开始

public class UserNotification : Work
{
    public override void Execute(IWorkContext iwc)
    {
        throw new InvalidWorkException($"some message:{iwc.Name} and :{iwc.Dept}");
    }
}

编辑:使用 MSTest 进行单元测试

因为您的标题特别提到您正在尝试测试具有void返回类型的方法; 我推断您已经用实际返回值测试了方法,因此您已经有了一个测试项目并且知道如何在编写测试后运行测试。 如果不; Mithgroth 写的答案很好地解释了如何开始一般测试。


您的测试由您希望测试的行为定义。 您的代码段没有任何行为,因此很难给您具体的答案。

我选择重写你的例子:

public class UserNotification : Work
{
    public override void Execute(IWorkContext iwc)
    {
        var splines = iwc.GetSplines();

        iwc.Reticulate(splines);
    }
}

现在我们有一些我们想要测试的行为。 测试目标是回答以下问题:

调用Execute时, UserNotification获取所需的样条并将它们网状化?

进行单元测试时,您想模拟所有其他东西 在这种情况下, IWorkContext是一个外部依赖项,因此应该对其进行模拟。 Mocking 工作上下文使我们能够轻松配置模拟以帮助进行测试。 当我们运行测试时,我们将传递一个充当间谍的IWorkContext object。 本质上,这个模拟的 object 将:

  • ...已设置为返回一组非常具体的样条,我们为测试目的选择的样条。
  • ...秘密记录对Reticulate方法的任何调用,并跟踪传递给它的参数。

在深入了解如何模拟之前,我们已经可以概述我们的测试将如何进行到 go:

[Test]
public void ReticulatesTheContextSplines()
{
    // Arrange
    IWorkContext mockedContext = ...; // This comes later
    UserNotification userNotification = new UserNotification();

    // Act
    userNotification.Execute(mockedContext);

    // Assert
    // Confirm that Reticulate() was called
    // Confirm that Reticulate() was given the result from `GetSplines()`
     
}

这是你的基本单元测试。 剩下的就是创建我们的模拟。

如果你愿意,你可以自己写这个。 只需创建一个实现IWorkContext的新 class ,并为其提供更多公共属性/方法以帮助您跟踪事物。 一个非常简单的例子是:

public class MockedWorkContext : IWorkContext
{
    // Allows the test to set the returned result
    public IEnumerable<Spline> Splines { get; set; }

    // History of arguments used for calls made to Reticulate. 
    // Each call will add an entry to the list.
    public List<IEnumerable<Spline>> ReticulateArguments { get; private set; } = new List<IEnumerable<Spline>>();

    public IEnumerable<Spline> GetSplines()
    {
        // Returns the preset splines that the test configured
        return this.Splines;
    }

    // Mocked implementation of Reticulate()
    public void Reticulate(IEnumerable<Spline> splines)
    {
        // Does nothing except record what you passed into it
        this.ReticulateArguments.Add(splines);
    }
}

这是一个非常简化的实现,但它完成了工作。 测试现在看起来像这样:

[Test]
public void ReticulatesTheContextSplines()
{
    // Arrange
    IEnumerable<Spline> splines = new List<Spline>() { new Spline(), new Spline() }; // Just create some items here, it's random test data.
    IWorkContext mockedContext = new MockedWorkContext();
    mockedContext.Splines = splines;

    UserNotification userNotification = new UserNotification();

    // Act
    userNotification.Execute(mockedContext);

    // Assert - Confirm that Reticulate() was called
    mockedContext.ReticulateArguments.Should().HaveCount(1);

    // Confirm that Reticulate() was given the result from `GetSplines()`
    mockedContext.ReticulateArguments[0].Should().BeEquivalentTo(splines);
     
}

该测试现在可以准确地测试您的方法的行为。 它使用模拟上下文作为间谍来报告您的被测单元(即UserNotification )对您传递给的上下文所做的事情。

请注意,我在这里使用的是 FluentAssertions,因为我发现它是最易读的语法。 随意使用您自己的断言逻辑。

虽然您可以编写自己的模拟; 有 mocking 个库可以帮助减少样板。 据我所知,Moq 和 NSubstitute 是最受欢迎的两个。 我个人更喜欢 NSubstitute 的语法; 但两者都同样出色地完成了工作。

首先,您需要一个测试项目以及您的常规项目。 你可以从这三个中选择:

  • MS测试
  • n单位
  • x单位

所有这些都应该在VS2022中有一个项目模板。

xUnit 是一个流行的,所以让我们选择它。 测试项目的通常命名约定是YourProject.Tests UnitTest1.cs class 重命名为UserNotificationTests.cs

就这么简单,您现在可以开始编写测试了。 在 xUnit 中,具有[Fact]属性的方法是测试方法。

using Xunit;

namespace MyProject.Tests
{
    public class UserNotificationTests
    {
        [Fact]
        public void Execute_Should_Throw_InvalidWorkException_With_Message()
        {

        }
    }
}

不要把这些方法当成代码中的方法,命名要接近英文句子,要像正则句一样能显露意图。

单元测试的经典方法分为三个阶段:

  • 安排:获取对象的实例,设置预期的 output,模拟依赖项,让它们准备就绪。
  • Act:调用你要测试的实际动作。
  • 断言:检查您的实际 output 是否与您预期的 output 相关。

让我们从安排开始吧。

  • 我们需要一个新的 UserNotification 实例UserNotification以便我们可以调用Execute()
  • 我们需要任何虚拟IWorkContext object 以便我们可以传递它。 为此,我们将使用NSubstitute 库
// Don't forget to add using NSubstitute

// Arrange
var userNotification = new UserNotification();
var workContext = Substitute.For<IWorkContext>();
workContext.Name = "testName";
workContext.Dept = "testDept";

现在你行动,并调用你的方法:

// Act
Action act = () => userNotification.Execute(workContext);

最后我们断言。 我强烈推荐用于断言的FluentAssertations 库

// Assert
act.Should().Throw<InvalidWorkException>()
.WithMessage($"some message:{workContext.Name} and :{workContext.Dept}");

导航到 View > Test Explorer 并运行您的测试,您应该会看到类似于此的内容:

在此处输入图像描述

恭喜,您编写了第一个单元测试。 这是测试代码的最终版本:

using FluentAssertions;
using NSubstitute;
using System;
using Xunit;

namespace MyProject.Tests
{
    public class UserNotificationTests
    {
        [Fact]
        public void Execute_Should_Throw_InvalidWorkException_With_Message()
        {
            // Arrange
            var userNotification = new UserNotification();
            var workContext = Substitute.For<IWorkContext>();
            workContext.Name = "testName";
            workContext.Dept = "testDept";

            // Act
            Action act = () => userNotification.Execute(workContext);

            // Assert
            act.Should().Throw<InvalidWorkException>()
                .WithMessage($"some message:{workContext.Name} and :{workContext.Dept}");
        }
    }

    public class UserNotification : Work
    {
        public override void Execute(IWorkContext iwc)
        {
            throw new InvalidWorkException($"some message:{iwc.Name} and :{iwc.Dept}");
        }
    }

    public abstract class Work
    {
        public virtual void Execute(IWorkContext iwc) { }
    }

    public interface IWorkContext 
    {
        public string Name { get; set; }
        public string Dept { get; set; }
    }

    public class InvalidWorkException : System.Exception
    {
        public InvalidWorkException() { }
        public InvalidWorkException(string message) : base(message) { }
        public InvalidWorkException(string message, System.Exception inner) : base(message, inner) { }
        protected InvalidWorkException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
    }
}

编写测试感觉与编写常规代码有很大不同。 但随着时间的推移,你会掌握它的窍门。 如何模拟、如何行动、如何断言,这些可能因您测试的内容而异。 要点是隔离你想要单元测试的主要东西,并模拟rest。

祝你好运!

如果你想使用 nunit,带有示例的文档很容易理解,链接如下。

Nunit 文档

而且我认为所有其他单元测试框架都与此类似。

[Test]
public void Execute_WhenCalled_ThrowArgumentException()
{
    //Initialize an instance of IWorkContext 
    var iwc = new WorkContext();
    //or use a Mock object, later on in assert use
    //userNotification.Execute(iwc.Object) 
    var iwc = new Mock<IWorkContext>();

    var userNotification = new UserNotification();

    Assert.Throws(typeof(InvalidWorkException), () =>
    {     
        userNotification.Execute(iwc) 
    });
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM