单元测试是否已使用 Moq 调用 System.Threading.Timer 构造函数 (TimerCallback) 中使用的方法

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

我希望确保将回调传递到签名中带有 TimerCallback 的 System.Threading.Timer 中:

public Timer(System.Threading.TimerCallback回调,对象?状态,TimeSpan dueTime,TimeSpan period);

我有一个持有计时器的类,需要一种方法来确保回调 (

MethodC
) 具有测试覆盖率。另外,我不会等 15 分钟来看看
MethodC
会发生什么。

using System.Threading;
public class MyClass : IDisposable {


  private Timer myTimer;
  
  public MyClass(){}

  public void MethodA(){
    MethodB();
  }

  private void MethodB(){
    this.myTimer = new Timer(this.MethodC, // TimerCallback
                             new Object(), 
                             TimeSpan.FromMinutes(15), 
                             TimeSpan.FromMinutes(15))
    // do some things
  }

  private void MethodC(){
    // do something
  }
  
}

根据我的搜索,答案似乎是将计时器移至接口,然后模拟接口。我正在使用起订量。 然而,我仍然对实现我的目标缺乏一些理解。如果在

MethodC
的构造函数中给出回调,我不知道如何测试
myTimer

我最终想检查是否通过 Moq 的

Verify
以及其他行为达到了代码。

我尝试将计时器代码移动到界面:

    interface ITimer
    {
        void SetTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period);
        void Dispose();
    }

    class MyTimer : ITimer, IDisposable
    {
        private Timer timer;
        private TimerCallback callback;

        public MyTimer(TimerCallback callback, Object state, TimeSpan dueTime, TimeSpan period)
        {
            this.callback = callback;
            this.SetTimer(this.callback, state, dueTime, period);
        }

        public void SetTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
        {
            this.timer = new Timer(callback, state, dueTime, period);
        }

        public void Dispose()
        {
            timer.Dispose();
        }
    }


.......



using System.Threading;
public class MyClass : IDisposable {


  private ITimer myTimer;
  
  public MyClass(){}

  public void MethodA(){
    MethodB();
  }

  private void MethodB(){
    this.myTimer = new MyTimer(this.MethodC, // TimerCallback
                             new Object(), 
                             TimeSpan.FromMinutes(15), 
                             TimeSpan.FromMinutes(15))
    // do some things
  }

  private void MethodC(){
    // do something
  }
  
}

计时器回调通过构造函数传递给

Timer
对象。没有方法可以调用,因此 Moq 的
Setup(Expression<Action<T>> expression)
在这里似乎不起作用。调用一定时间后,回调
MethodC
只是在另一个线程上调用,而且它是私有的。

我尝试让我的

MyTimer
类在另一种方法中创建
Timer
,但是我想测试
MethodC
,而不是
SetTimer

设计看起来很复杂且无法测试。但我对起订量也不太熟悉。有没有关于如何改进这一点的建议,以便我可以测试

MethodC

c# timer callback moq
1个回答
0
投票

如果您想测试 MethodC 的功能,一种选择是将 MethodC 标记为

internal
而不是
private
,然后使内部结构对您的单元测试项目可见。对于 .Net Core,这相当简单,特别是如果您遵循一致的约定(例如使用 ProjectName.UnitTests.csproj 进行 ProjectName.csproj 项目的单元测试)。添加到 ProjectName.csproj 的以下组将允许单元测试和集成测试项目查看内部结构:

这对于您有一些逻辑可以从非公开且不方便公开的测试中受益的情况很有帮助。需要注意的是,这是特定于单元测试的代码,您应该尽可能避免。然而,我认为这更多的是一个指导方针,而不是一条规则。如果与尝试消除所有气味的替代重构的成本相比,好处大于气味,那么它可能是值得的。我通常会将合法的内部结构限制为我想要测试的程序集。保留方法

private
的选项,我们不希望它被作为
internal
访问,然后您可以使用内部测试适配器:

private MethodC(/* parameters */) 
{
    // ...
}

internal MethodC_TestAdapter(/* parameters */)
{
#if TEST
     MethodC( /* parameters */ )
#else
     throw new InvalidOperation($"{nameof(MethodC_TestAdapter)} should never be called in non-testing code.");
#end if
}

其中 TEST 是一个预处理器,仅应在本地运行和自动化构建的单元测试配置时定义。这样,如果有人后来在他们的智能感知中找到“MethodC_TestAdapter”并调用它,他们应该会收到一个带有解释的异常。

也就是说,编写高度可测试的代码的部分挑战是在构建代码时遵循良好的编码原则。理想情况下,如果 MethodC 实际上是一个处理程序,您可能希望解耦 MethodB 和 MethodC 之间的状态。 如果 MethodC 改变了此类中的公共状态,那就有点问题了,因为在任何给定时间该状态可能是什么,以及方法 C 和其他可能执行的方法所做的更新之间的竞争,这是一种兼顾的情况。因此,例如设置标志、递增或更改值等。如果 MethodC 可以被隔离为无状态或仅管理其自己的状态,则 MethodC 可以移出到它自己的类中,在该类中可以构造实例或将实例注入到 MethodB 所在的类中生活。 MethodB 可以建立一个链接到依赖实例 MethodC 的计时器。通过这种方式,可以测试其服务上的 MethodC 的行为,而无需担心访问修饰符。面向对象语言处理状态,但目标是尽可能地将其划分。

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