[英]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 的语法; 但两者都同样出色地完成了工作。
首先,您需要一个测试项目以及您的常规项目。 你可以从这三个中选择:
所有这些都应该在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()
{
}
}
}
不要把这些方法当成代码中的方法,命名要接近英文句子,要像正则句一样能显露意图。
单元测试的经典方法分为三个阶段:
让我们从安排开始吧。
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,带有示例的文档很容易理解,链接如下。
而且我认为所有其他单元测试框架都与此类似。
[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.