Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/283.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何处理TDD异步事件?_C#_Events_Asynchronous_Tdd - Fatal编程技术网

C# 如何处理TDD异步事件?

C# 如何处理TDD异步事件?,c#,events,asynchronous,tdd,C#,Events,Asynchronous,Tdd,基本问题是,我如何创建一个需要调用方法的单元测试,等待被测试类上发生事件,然后调用另一个方法(我们实际想要测试的方法) 如果您有时间进一步阅读,以下是场景: 我正在开发一个必须控制一个硬件的应用程序。为了避免硬件可用性的依赖性,当我创建我的对象时,我指定我们在测试模式下运行。当这种情况发生时,被测试的类将创建适当的驱动程序层次结构(在本例中是硬件驱动程序的一个薄层模拟层) 假设所讨论的课程是一部电梯,我想测试给我电梯所在楼层编号的方法。下面是我的虚拟测试现在的样子: [TestMethod] p

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

如果您有时间进一步阅读,以下是场景:

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

假设所讨论的课程是一部电梯,我想测试给我电梯所在楼层编号的方法。下面是我的虚拟测试现在的样子:

[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);
        }
    }

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

为此,您可以使用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);
    }
}

(这里的
Assert.Fail()
调用应该替换为单元测试工具用于显式失败测试的任何机制,或者您可以抛出异常。)

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

因此,您的第一组测试可以测试GoToFloor()是否将状态设置为“移动”,以及它移动的方向是否正确

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


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

这是我类似的方法

    [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()调用的返回值,以检查是否调用了事件处理程序。

我确实不喜欢上面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秒内到达吗?更改超时。

在目标操作系统上调用时是否会被阻塞?否。一旦调用。GoToFloor(楼层)它将返回,但电梯将状态更改为“移动”,需要一段时间才能到达该楼层,然后它将引发一个事件(可能在不同的线程中)Hi,这应该可以解决我在测试AYSN同步操作时遇到的问题。我使用了上面的代码作为起点,但是Monitor.Pulse不会导致等待重新获取锁,它会超时,从而触发Assert.Fail(“事件未到达”)。你知道为什么会这样吗。。。!?上面编辑中的解决方案是在Monitor.Pulse周围缺少一个
。你是对的,我的电梯确实实现了状态模式,我已经在做这些测试了。我只是搞不清楚在这个特定的案例中,等待是否是最好的选择。啊,对于这个特定的案例,我会说不。我不想说不,因为可能(可能?)有一些好的案例用于测试计时器/超时等等。但是在这里,你“等待”的是你的硬件来做一些工作,这不是你试图进行的单元测试。这就是我正在做的,除了我用一个WaitHandle和一个lambda来做它,而不是用一个分配给事件的方法,但是想法是完全一样的。嗨,这应该解决了我在测试一个AYSN同步操作时遇到的一个问题。我使用了上面的代码作为起点,但是Monitor.Pulse不会导致等待重新获取锁,它会超时,从而触发Assert.Fail(“事件未到达”)。你知道为什么会这样吗@Kildareflare:脉冲导致Monitor.Wait()结束等待。请注意,当Monitor.Wait()结束等待(由于脉冲或超时)时,它必须重新获取锁,因此可能是Monitor.Wait()已经计时,但稍后当它能够获取锁时,您会看到消息。我猜你把超时设置得太小了:以毫秒为单位,你是不是改为指定秒数?@Paul。超时时间以毫秒(5000)为单位。每层乘电梯250ms。发生的情况是,电梯在一秒钟多一点的时间内到达5楼,并触发电梯到达事件。然后,测试将一直进行到Monitor.Wait Timeout导致测试失败为止。顺便说一下,我可以使用下面的ManualResetEvent使备用版本正常工作。@Kildareflare:我想我知道出了什么问题。你的GoToFloor()实现:我打赌你是用它来提升电梯到达地面防护?如果是这样,它将使用随后等待事件的同一线程。按照线程执行:它将(1)获得锁,(2)调用GoToFloor()方法,(3)引发事件,该事件转换为对TestelevatorArrized的调用,(4)触发监视器,(5)从TestelevatorArrized()方法返回,(6)从GoToFloor()调用返回,(7)开始等待已经错过的脉冲。