【问题标题】:Unittests testing timers fail indeterministically on TFS单元测试测试计时器在 TFS 上确定性失败
【发布时间】:2013-02-26 10:41:05
【问题描述】:

我们的项目确实有一堆单元测试,它们也在 TFS 2012 构建服务器上签入期间运行。虽然它们在本地的行为与预期的非常相似,但有时在 TFS 上使用计时器的单元测试会发生一些奇怪的行为。最后,我们编写了一个类和一个测试来隔离这种行为。

班级:

public class TestTimerOnServer    
{    
    Timer _MyTimer = new Timer(100);    
    int _CountsToDo = 100;    
    public int TimerElapsedCounter = 0;

    public TestTimerOnServer()    
    {    
        _MyTimer.Elapsed += _MyTimer_Elapsed;    
    }

    public void StartCounting(int argNumberOfCounts, int argIntervalInMs)    
    {    
        _MyTimer.Stop();    
        _MyTimer.Interval = argIntervalInMs;    
        TimerElapsedCounter = 0;    
        _CountsToDo = argNumberOfCounts;    
        _MyTimer.Start();    
    }

    void _MyTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)    
    {    
        if (_CountsToDo > TimerElapsedCounter)    
            TimerElapsedCounter++;    
        else    
            _MyTimer.Stop();    
    }    
}

针对它运行的测试:

private void TestTimerServerBehaviour()   
{   
   TestTimerOnServer ttos = new TestTimerOnServer();   
   ttos.StartCounting(10, 100); // should lead 1000 ms

   // we wait 1100 MS   
   Thread.Sleep(1100);

   Assert.AreEqual(ttos.TimerElapsedCounter, 10,"We should have 10 
   samples, but only " + ttos.TimerElapsedCounter + " have been generated.!"); 

   ttos.StartCounting(50, 100); // should lead 5000 ms

   // we wait 6000 MS   
   Thread.Sleep(6000);

   Assert.AreEqual(ttos.TimerElapsedCounter, 50, "We should have 50 
   samples, but only " + ttos.TimerElapsedCounter + " have been generated.!");   
}

大多数情况下,此测试通过,有时它在 TFS 上失败 - 仅触发事件,例如两次而不是 50 次。所以问题是我们在这里遇到了一般的单元测试设计缺陷,还是在 TFS 构建服务运行测试时发现问题?

编辑: TFS 在 VM 上运行 - 这可能是我们问题的根源吗?

EDIT2 - 最终调查结果 问题显然是测试上下文和 VMware 的结合。我使用@allen 发布的代码编写了一个小应用程序,一个使用相同代码的测试和另一个导致cpu 负载的测试:

void CPULoader()
{
    var action = new Action(() => { while (true);});

    Parallel.Invoke(action, action, action, action, action, action, action, action); 
}

相当原始但高效。

在我运行应用程序的硬件机器上,CPU-Loader 会导致计时器事件稍后发生,例如1100 毫秒而不是 1000 毫秒。到目前为止还不错,毕竟 Win7 不是实时系统。

在 VMware 上,某些组合会导致延迟高达 5000 毫秒,而应该是一秒... - 当然更糟,但事件仍然会发生。

真正的乐趣始于运行测试(为此我将代码打包成一个无限循环),然后启动 CPU-Loader。整个系统非常缓慢,直到我停止加载 CPU 并且 在测试之前没有发生事件

希望这可以给某人一些提示 - 我们通过使用 Timer 抽象并在单元测试时模拟计时行为来修复我们的测试。作为副作用,测试现在运行得更快:)

【问题讨论】:

  • 是的,确实 - TFS 服务器正在虚拟机上运行 - 我将把它添加到问题中。
  • 这是哪个 Timer 类?表单、计时器还是线程? msdn.microsoft.com/en-us/magazine/cc164015.aspx
  • 它是 System.Timers.Timer ,顺便说一句,它是唯一一个具有构造函数 Timer(双间隔)的。但我知道我应该指出这一点。

标签: unit-testing timer


【解决方案1】:

sleep 和亲属的系统调用经常与信号处理程序交互并提前中止它们的睡眠。对于 Unix sleep(3) 函数,该现象得到了最好的记录,但我怀疑它也适用于您的 API。 您应该通过在睡眠后检查当前时间来防止短睡眠。

通常,您希望将计时器单元测试与系统时钟尽可能分离。在处理基于队列的计时器和可以从外部调用的心跳函数时,我有很好的经验,但是您应该始终能够模拟对 sleep 和时间获取函数的调用,然后在不调用的情况下进行测试真正的功能。

【讨论】:

    【解决方案2】:

    我怀疑虚拟机。使用 VM,您可能会遇到与时间同步相关的问题,这些问题可能会导致与时间相关的功能遇到问题。验证这一点的一种方法是编写一段简单的代码并在同一个 VM 上运行它。

    Stopwatch watch = new Stopwatch();
    System.Timers.Timer timer = new System.Timers.Timer();
    timer.Interval = 30*60 * 1000;
    timer.Elapsed += timer_Elapsed;
    watch.Start();
    timer.Start();
    
    static void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        watch.Stop();
        Console.WriteLine("Stopwatch value - {0}", watch.Elapsed);
        watch.Restart();
    }
    

    stackoverflow 上还有另一个例子System.Diagnostics.Stopwatch returns negative numbers in Elapsed... properties

    可能导致这种情况的一些因素是 a) 在 VM 上禁用时间同步服务 b) 主机上缺乏资源,例如。虚拟机太多

    我的建议是在主机上设置一个新的虚拟机,并为该虚拟机提供更多资源

    【讨论】:

    • 缺乏资源似乎不是问题——我将构建服务移至专用机器,但问题仍然存在。所以更多的资源似乎使问题更加罕见,但仍然没有消失。
    猜你喜欢
    • 2010-11-28
    • 1970-01-01
    • 2011-10-05
    • 1970-01-01
    • 2016-06-09
    • 1970-01-01
    • 2011-05-22
    • 2023-03-06
    • 1970-01-01
    相关资源
    最近更新 更多