代码之家  ›  专栏  ›  技术社区  ›  Padu Merloti

如何TDD异步事件?

  •  11
  • Padu Merloti  · 技术社区  · 16 年前

    最基本的问题是,我如何创建一个单元测试,它需要调用一个方法,等待一个事件发生在被测试的类上,然后调用另一个方法(我们实际上想要测试的那个方法)?

    如果您有时间进一步阅读以下内容:

    我正在开发一个必须控制硬件的应用程序。为了避免硬件可用性的依赖性,在创建对象时,我指定在测试模式下运行。发生这种情况时,正在测试的类将创建适当的驱动程序层次结构(在本例中是硬件驱动程序的一个瘦模拟层)。

    假设所讨论的类是一个电梯,我想测试一种方法,该方法给出电梯所在的楼层号。下面是我的虚拟测试现在的样子:

    [TestMethod]
    public void TestGetCurrentFloor()
    {
        var elevator = new Elevator(Elevator.Environment.Offline);
        elevator.ElevatorArrivedOnFloor += TestElevatorArrived;
    
        elevator.GoToFloor(5);
    
        //Here's where I'm getting lost... I could block
        //until TestElevatorArrived gives me a signal, but
        //I'm not sure it's the best way
    
        int floor = elevator.GetCurrentFloor();
    
        Assert.AreEqual(floor, 5);
    }
    

    编辑:

    谢谢你的回答。这就是我最终实现它的方式:

        [TestMethod]
        public void TestGetCurrentFloor()
        {
            var elevator = new Elevator(Elevator.Environment.Offline);
            elevator.ElevatorArrivedOnFloor += (s, e) => { Monitor.Pulse(this); };
    
            lock (this)
            {
                elevator.GoToFloor(5);
    
                if (!Monitor.Wait(this, Timeout))
                    Assert.Fail("Elevator did not reach destination in time");
    
                int floor = elevator.GetCurrentFloor();
    
                Assert.AreEqual(floor, 5);
            }
        }
    
    4 回复  |  直到 15 年前
        1
  •  5
  •   Paul Ruane    15 年前

    我想你已经说对了。测试需要等到事件发生或者您判断到达时间太长,应该放弃等待。

    要做到这一点,您可以使用monitor.wait和超时测试,并让它与monitor.pulse信号当事件到达。

    
    [TestMethod]
    public void TestGetCurrentFloor()
    {
        var elevator = new Elevator(Elevator.Environment.Offline);
        elevator.ElevatorArrivedOnFloor += TestElevatorArrived;
    
        lock (this)
        {
            elevator.GoToFloor(5); // NOTE: this must hand off to a second thread, and the ElevatorArrivedOnFloor must be raised by this other thread otherwise the Monitor will be pulse before we've started waiting for it
    
            if (!Monitor.Wait(this, TIMEOUT)) Assert.Fail("Event did not arrive in time.");
        }
    
        int floor = elevator.GetCurrentFloor();
    
        Assert.AreEqual(floor, 5);
    }
    
    private void TestElevatorArrived(int floor)
    {
        lock (this)
        {
            Monitor.Pulse(this);
        }
    }
    
    

    (The Assert.Fail() 这里的调用应该替换为单元测试工具用于显式失败测试或引发异常的任何机制。)

        2
  •  2
  •   Tanzelax    16 年前

    也许这只是一个糟糕的例子,但是你的电梯听起来更像一个状态机,而不是异步处理的东西。

    所以第一组测试可以测试gotoflor()是否将状态设置为moving,以及它的移动方向是否正确。

    接下来的一组测试将在testelevatorArrived()上进行,并将测试如果您的状态正在向某一楼层移动,则实际的移动(即,在异步等待之后调用的函数,或触发“moved”事件的硬件处理程序)将把状态设置为预期楼层。

    否则,您测试的很可能是您对硬件的模拟正确地模拟了计时和移动,这似乎不正确。

        3
  •  2
  •   Andrew Bienert    16 年前

    这是我类似的方法。

        [TestMethod]
        public void TestGetCurrentFloor()
        {
            var completedSync = new ManualResetEvent(false);
            var elevator = new Elevator(Elevator.Environment.Offline);
    
            elevator.ElevatorArrivedOnFloor += delegate(object sender, EventArgs e)
            {
                completedSync.Set();
            };
    
            elevator.GoToFloor(5);
    
            completedSync.WaitOne(SOME_TIMEOUT_VALUE);
    
            int floor = elevator.GetCurrentFloor();
    
            Assert.AreEqual(floor, 5);
        } 
    

    还可以测试waitOne()调用的返回值,以检查是否调用了事件处理程序。

        4
  •  0
  •   Josh Smeaton    15 年前

    我真的不喜欢上面monitor.pulse/wait方法的比赛状态。

    一个不太好但有效的方法是这样的:

    [TestMethod]
    public void TestGetCurrentFloor()
    {
        // NUnit has something very similar to this, I'm going from memory
        this.TestCounter.reset(); 
    
        var elevator = new Elevator(Elevator.Environment.Offline);
        elevator.ElevatorArrivedOnFloor += (s,e) => { Assert.That(e.floor).Is(5) }
    
        elevator.GoToFloor(5);
    
        // It should complete within 5 seconds..
        Thread.Sleep(1000 * 5);
        Assert.That(elevator.GetCurrentFloor()).Is(5);
    
        Assert.That(this.TestCounter.Count).Is(2);
    }
    

    我不喜欢这个解决方案,因为如果电梯在500毫秒内到达,你就得再等4500毫秒。如果你有很多这样的测试,而且你希望测试很快,我会完全避免这种情况。但是,这种测试也可以作为性能/健全性检查的双重功能。

    想确保电梯在2秒内到达吗?更改超时。