在单元测试中模拟CancellationToken.IsCancellationRequested

问题描述 投票:1回答:2

我想测试应该连续运行直到被杀死的任务。假设正在测试以下方法:

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
}

问题是任务从未终止,因为从未请求取消。最终,我想创建一个像CancellationTokenMockRepository.GenerateStub<CancellationToken>()存根,并告诉它对IsCancellationRequested的哪个调用返回true,但是CancellationToken不是引用类型,因此不可能。

所以问题是如何进行测试,其中Run执行n迭代然后终止?是否可以不重构Run

c# unit-testing asynchronous rhino-mocks
2个回答
1
投票

这取决于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));
    }
}

0
投票

在不改变代码的情况下,您能做的最好的事情是在特定时间后取消。 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的测试次数,但这是不可能的。

© www.soinside.com 2019 - 2024. All rights reserved.