单元测试时模拟 CancellationToken.IsCancellationRequested
Simulating CancellationToken.IsCancellationRequested when unit testing
我想测试一个应该 运行 一直持续到终止的任务。假设正在测试以下方法:
public class Worker
{
public async Task Run(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
// do something like claim a resource
}
catch (Exception e)
{
// catch exceptions and print to the log
}
finally
{
// release the resource
}
}
}
}
还有一个测试用例
[TestCase]
public async System.Threading.Tasks.Task Run_ShallAlwaysReleaseResources()
{
// Act
await domainStateSerializationWorker.Run(new CancellationToken());
// Assert
// assert that resource release has been called
}
问题是任务永远不会终止,因为从未请求取消。最终我想创建一个像 MockRepository.GenerateStub<CancellationToken>()
这样的 CancellationToken
存根并告诉它在哪个调用 IsCancellationRequested
return true
上,但是 CancellationToken
不是引用类型,因此不可能。
所以问题是如何进行 Run
执行 n
次迭代然后终止的测试?不重构可以吗Run
?
在不更改代码的情况下,您最多只能在特定时间后取消。 CancellationTokenSource.CancelAfter()
方法使这变得简单:
[TestCase]
public async System.Threading.Tasks.Task Run_ShallAlwaysReleaseResources()
{
// Signal cancellation after 5 seconds
var cts = new TestCancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
// Act
await domainStateSerializationWorker.Run(cts.Token);
// Assert
// assert that resource release has been called
}
您的代码编写方式(每次迭代仅检查 IsCancellationRequested
一次)意味着取消将在 一些 次完整迭代后发生。只是不会每次都是相同的数字。
如果您想在特定次数的迭代后取消,那么您唯一的选择是修改您的代码以跟踪发生了多少次迭代。
我想我可以创建一个继承自 CancellationTokenSource
的新 class 来跟踪 IsCancellationRequested
已经测试了多少次,但这是不可能的做。
这取决于Run
中的运行。如果有一些注入依赖
例如
public interface IDependency {
Task DoSomething();
}
public class Worker {
private readonly IDependency dependency;
public Worker(IDependency dependency) {
this.dependency = dependency;
}
public async Task Run(CancellationToken cancellationToken) {
while (!cancellationToken.IsCancellationRequested) {
try {
// do something like claim a resource
await dependency.DoSomething();
} catch (Exception e) {
// catch exceptions and print to the log
} finally {
// release the resource
}
}
}
}
然后可以对其进行模拟和监控,以计算某个成员被调用的次数。
[TestClass]
public class WorkerTests {
[TestMethod]
public async Task Sohuld_Cancel_Run() {
//Arrange
int expectedCount = 5;
int count = 0;
CancellationTokenSource cts = new CancellationTokenSource();
var mock = new Mock<IDependency>();
mock.Setup(_ => _.DoSomething())
.Callback(() => {
count++;
if (count == expectedCount)
cts.Cancel();
})
.Returns(() => Task.FromResult<object>(null));
var worker = new Worker(mock.Object);
//Act
await worker.Run(cts.Token);
//Assert
mock.Verify(_ => _.DoSomething(), Times.Exactly(expectedCount));
}
}
我想测试一个应该 运行 一直持续到终止的任务。假设正在测试以下方法:
public class Worker
{
public async Task Run(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
// do something like claim a resource
}
catch (Exception e)
{
// catch exceptions and print to the log
}
finally
{
// release the resource
}
}
}
}
还有一个测试用例
[TestCase]
public async System.Threading.Tasks.Task Run_ShallAlwaysReleaseResources()
{
// Act
await domainStateSerializationWorker.Run(new CancellationToken());
// Assert
// assert that resource release has been called
}
问题是任务永远不会终止,因为从未请求取消。最终我想创建一个像 MockRepository.GenerateStub<CancellationToken>()
这样的 CancellationToken
存根并告诉它在哪个调用 IsCancellationRequested
return true
上,但是 CancellationToken
不是引用类型,因此不可能。
所以问题是如何进行 Run
执行 n
次迭代然后终止的测试?不重构可以吗Run
?
在不更改代码的情况下,您最多只能在特定时间后取消。 CancellationTokenSource.CancelAfter()
方法使这变得简单:
[TestCase]
public async System.Threading.Tasks.Task Run_ShallAlwaysReleaseResources()
{
// Signal cancellation after 5 seconds
var cts = new TestCancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
// Act
await domainStateSerializationWorker.Run(cts.Token);
// Assert
// assert that resource release has been called
}
您的代码编写方式(每次迭代仅检查 IsCancellationRequested
一次)意味着取消将在 一些 次完整迭代后发生。只是不会每次都是相同的数字。
如果您想在特定次数的迭代后取消,那么您唯一的选择是修改您的代码以跟踪发生了多少次迭代。
我想我可以创建一个继承自 CancellationTokenSource
的新 class 来跟踪 IsCancellationRequested
已经测试了多少次,但这是不可能的做。
这取决于Run
中的运行。如果有一些注入依赖
例如
public interface IDependency {
Task DoSomething();
}
public class Worker {
private readonly IDependency dependency;
public Worker(IDependency dependency) {
this.dependency = dependency;
}
public async Task Run(CancellationToken cancellationToken) {
while (!cancellationToken.IsCancellationRequested) {
try {
// do something like claim a resource
await dependency.DoSomething();
} catch (Exception e) {
// catch exceptions and print to the log
} finally {
// release the resource
}
}
}
}
然后可以对其进行模拟和监控,以计算某个成员被调用的次数。
[TestClass]
public class WorkerTests {
[TestMethod]
public async Task Sohuld_Cancel_Run() {
//Arrange
int expectedCount = 5;
int count = 0;
CancellationTokenSource cts = new CancellationTokenSource();
var mock = new Mock<IDependency>();
mock.Setup(_ => _.DoSomething())
.Callback(() => {
count++;
if (count == expectedCount)
cts.Cancel();
})
.Returns(() => Task.FromResult<object>(null));
var worker = new Worker(mock.Object);
//Act
await worker.Run(cts.Token);
//Assert
mock.Verify(_ => _.DoSomething(), Times.Exactly(expectedCount));
}
}