单元测试无限轮询方法
unit testing an infinite polling method
我有一个方法,当应用程序启动时在新任务中启动,并在它结束时停止。该方法每半秒执行一些逻辑。我不知道该如何测试它 - 有线索吗?
public async Task StartPoll(CancellationToken token)
{
while(!token.IsCancellationRequested)
{
try
{
var dict = dependecy.getDictionary();
var model = Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(dict));
udpDistributer.Send(model, model.Length, _distributionEndPoint);
}
finally
{
await Task.Delay(500, Token);
}
}
}
其中 udpDistributer
是来自 DI 的 new UdpClient(0)
,_distributionEndPoint
是 IPEndPoint
基本上,我想检查逻辑是否有效,以及是否以正确的时间间隔发送了正确的数据。我如何使用 xunit 执行此操作?
编辑
我已经做到了:
// arrange
...
cts = new CancellationTokenSource(timeout);
//act
await sut.StartPoll(cts.Token);
// assert
mockUdpDistributer.Verify(x => x.Send(expectedModel, expectedModel.length,
It.IsAny<IPEndPoint>()), Times.Exactly(expectedTimes));
但是,这会导致测试失败并显示 TaskCanceledException
您确认 udpDistributer.Send()
(我假设您已注入,如果没有,则这样做)在您需要的时间内被调用了适当的次数,然后取消令牌。
我建议将轮询逻辑与其余逻辑分开,并分别进行测试。轮询器可能难以进行单元测试,但它可能足够简单以至于单元测试不会增加足够的价值。
例如,一个非常简单的轮询器可能如下所示:
public class SimplePoller
{
private Timer timer;
public SimplePoller(TimeSpan interval)
{
timer = new Timer(interval.TotalMilliseconds);
timer.Elapsed += OnElapsed;
timer.Start();
}
private void OnElapsed(object sender, ElapsedEventArgs e) => Elapsed?.Invoke(this, e);
public void Dispose() => timer.Dispose();
public event EventHandler Elapsed;
public bool Enabled { get => timer.Enabled; set => timer.Enabled = value; }
}
请注意,这并不完全等同于您的轮询器,因为这将在每个时间间隔引发 elapsed 事件,而不管 Elapsed 事件需要多长时间 运行。
然后您可以为您的轮询器添加一个接口,允许它被模拟用于其他单元测试。然后您应该能够手动触发轮询事件并检查该方法是否按照预期进行。在某些情况下,将 DateTime.Now
方法包装在接口中以允许对其进行模拟也可能很有用。
此处的文档 async-programming-unit-testing-asynchronous-code 可能正是您要查找的内容。
实际上它所说的是使用异步测试方法并等待函数:
[TestMethod]
public async Task CorrectlyFailingTest()
{
await SystemUnderTest.FailAsync();
}
也尽量避免这样的签名 private async void StartPoll
。最好始终从异步代码中 return Task
。
作为如何测试调用的一部分,我还假设 udpDistributer
是实际实现的接口。所以你可以 mock/count/setup 模拟接口,以确保它按预期工作。 Using Moq to mock an asynchronous method for a unit test
要让测试在某个时候停止,您可以使用取消标记 cancel-async-tasks-after-a-period-of-time
UdpClient
应该尽可能包装在抽象中,StartPoll
应该 return Task
.
要测试是否实现了所需的行为,请在已知时间后使用令牌取消并检查 IUdpClient.Send
被调用了多少次
这是一个过于简化的例子
[TestFixture]
public class MyTestClass {
[Test]
public async Task MyTestMethod() {
//Arrange
int expectedTimes = 3;
var timeout = TimeSpan.FromMilliseconds(1800);
CancellationTokenSource cts = new CancellationTokenSource();
var mockUdpDistributer = new Mock<IUdpClient>();
var sut = new MyClass(mockUdpDistributer.Object);
//Act
Task start = sut.StartPoll(cts.Token);
await Task.Delay(timeout);
cts.Cancel();
//Assert
mockUdpDistributer
.Verify(x =>
x.Send(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IPEndPoint>())
, Times.AtLeast(expectedTimes)
);
}
}
使用抽象客户端
public interface IUdpClient {
void Send(byte[] datagram, int bytes, System.Net.IPEndPoint endPoint);
}
可以模拟并注入到被测对象中
class MyClass {
private readonly IUdpClient udpDistributer;
private readonly IPEndPoint _distributionEndPoint;
public MyClass(IUdpClient udpDistributer) {
this.udpDistributer = udpDistributer;
}
public async Task StartPoll(CancellationToken token) {
while (!token.IsCancellationRequested) {
try {
object dict = new object();//<-- dependecy.getDictionary();
byte[] model = Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(dict));
udpDistributer.Send(model, model.Length, _distributionEndPoint);
} finally {
await Task.Delay(500, token);
}
}
}
}
我有一个方法,当应用程序启动时在新任务中启动,并在它结束时停止。该方法每半秒执行一些逻辑。我不知道该如何测试它 - 有线索吗?
public async Task StartPoll(CancellationToken token)
{
while(!token.IsCancellationRequested)
{
try
{
var dict = dependecy.getDictionary();
var model = Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(dict));
udpDistributer.Send(model, model.Length, _distributionEndPoint);
}
finally
{
await Task.Delay(500, Token);
}
}
}
其中 udpDistributer
是来自 DI 的 new UdpClient(0)
,_distributionEndPoint
是 IPEndPoint
基本上,我想检查逻辑是否有效,以及是否以正确的时间间隔发送了正确的数据。我如何使用 xunit 执行此操作?
编辑
我已经做到了:
// arrange
...
cts = new CancellationTokenSource(timeout);
//act
await sut.StartPoll(cts.Token);
// assert
mockUdpDistributer.Verify(x => x.Send(expectedModel, expectedModel.length,
It.IsAny<IPEndPoint>()), Times.Exactly(expectedTimes));
但是,这会导致测试失败并显示 TaskCanceledException
您确认 udpDistributer.Send()
(我假设您已注入,如果没有,则这样做)在您需要的时间内被调用了适当的次数,然后取消令牌。
我建议将轮询逻辑与其余逻辑分开,并分别进行测试。轮询器可能难以进行单元测试,但它可能足够简单以至于单元测试不会增加足够的价值。
例如,一个非常简单的轮询器可能如下所示:
public class SimplePoller
{
private Timer timer;
public SimplePoller(TimeSpan interval)
{
timer = new Timer(interval.TotalMilliseconds);
timer.Elapsed += OnElapsed;
timer.Start();
}
private void OnElapsed(object sender, ElapsedEventArgs e) => Elapsed?.Invoke(this, e);
public void Dispose() => timer.Dispose();
public event EventHandler Elapsed;
public bool Enabled { get => timer.Enabled; set => timer.Enabled = value; }
}
请注意,这并不完全等同于您的轮询器,因为这将在每个时间间隔引发 elapsed 事件,而不管 Elapsed 事件需要多长时间 运行。
然后您可以为您的轮询器添加一个接口,允许它被模拟用于其他单元测试。然后您应该能够手动触发轮询事件并检查该方法是否按照预期进行。在某些情况下,将 DateTime.Now
方法包装在接口中以允许对其进行模拟也可能很有用。
此处的文档 async-programming-unit-testing-asynchronous-code 可能正是您要查找的内容。
实际上它所说的是使用异步测试方法并等待函数:
[TestMethod]
public async Task CorrectlyFailingTest()
{
await SystemUnderTest.FailAsync();
}
也尽量避免这样的签名 private async void StartPoll
。最好始终从异步代码中 return Task
。
作为如何测试调用的一部分,我还假设 udpDistributer
是实际实现的接口。所以你可以 mock/count/setup 模拟接口,以确保它按预期工作。 Using Moq to mock an asynchronous method for a unit test
要让测试在某个时候停止,您可以使用取消标记 cancel-async-tasks-after-a-period-of-time
UdpClient
应该尽可能包装在抽象中,StartPoll
应该 return Task
.
要测试是否实现了所需的行为,请在已知时间后使用令牌取消并检查 IUdpClient.Send
被调用了多少次
这是一个过于简化的例子
[TestFixture]
public class MyTestClass {
[Test]
public async Task MyTestMethod() {
//Arrange
int expectedTimes = 3;
var timeout = TimeSpan.FromMilliseconds(1800);
CancellationTokenSource cts = new CancellationTokenSource();
var mockUdpDistributer = new Mock<IUdpClient>();
var sut = new MyClass(mockUdpDistributer.Object);
//Act
Task start = sut.StartPoll(cts.Token);
await Task.Delay(timeout);
cts.Cancel();
//Assert
mockUdpDistributer
.Verify(x =>
x.Send(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IPEndPoint>())
, Times.AtLeast(expectedTimes)
);
}
}
使用抽象客户端
public interface IUdpClient {
void Send(byte[] datagram, int bytes, System.Net.IPEndPoint endPoint);
}
可以模拟并注入到被测对象中
class MyClass {
private readonly IUdpClient udpDistributer;
private readonly IPEndPoint _distributionEndPoint;
public MyClass(IUdpClient udpDistributer) {
this.udpDistributer = udpDistributer;
}
public async Task StartPoll(CancellationToken token) {
while (!token.IsCancellationRequested) {
try {
object dict = new object();//<-- dependecy.getDictionary();
byte[] model = Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(dict));
udpDistributer.Send(model, model.Length, _distributionEndPoint);
} finally {
await Task.Delay(500, token);
}
}
}
}