我想测试应该连续运行直到被杀死的任务。假设正在测试以下方法:
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
}
问题是任务从未终止,因为从未请求取消。最终,我想创建一个像CancellationToken
的MockRepository.GenerateStub<CancellationToken>()
存根,并告诉它对IsCancellationRequested
的哪个调用返回true
,但是CancellationToken
不是引用类型,因此不可能。
所以问题是如何进行测试,其中Run
执行n
迭代然后终止?是否可以不重构Run
?
这取决于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));
}
}
在不改变代码的情况下,您能做的最好的事情是在特定时间后取消。 CancellationTokenSource.CancelAfter()
方法使此操作变得简单:
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
}
)意味着取消将在some次完整迭代之后发生。每次都不会是相同的数字。
如果要在特定数量的迭代后取消,那么唯一的选择是修改代码以跟踪发生了多少次迭代。
我以为我也许可以创建一个继承自IsCancellationRequested
的新类,以跟踪CancellationTokenSource
的测试次数,但这是不可能的。