C#和Moq - 如何断言事件没有被触发

3

我有一个应用程序,根据Sim对象的状态引发事件。特别是,如果Sim状态改变-即从PinRequired到Accessible-OnStatus事件将被触发。这些事件通过新线程通知侦听器。

我在单元测试中使用Moq,并能够断言OnStatus是否已被触发:

        [Test]
    public void TestOnStatusIsRaised()
    {
        Sim sim = new Sim();

        sim.OnStatus += OnStatus;

        lock (this)
        {
            sim.UpdateSimInfo(new Info("a new status"));
            Monitor.Wait(this);
        }

        Assert.IsTrue(_onStatusCalled);
    }

    private void OnStatus(SimStatus obj)
    {
        lock (this)
        {
            _onStatusCalled = true;
            Monitor.Pulse(this);
        }
    }

正如您所见,使用Monitor类,我可以等待事件被触发后再进行断言,确保_onStatusCalled标志已设置为true。

当我想要断言事件未被触发时,我的困难就出现了。我无法使用Monitor等待事件不被触发,因为测试将永远等待!我可以添加一个超时来等待,但那似乎是一个肮脏的hack。

我尝试了以下方法:

        [Test]
    public void TestOnStatusIsNotFired()
    {
        Sim sim = new Sim();
        sim.OnStatus += onStatus => Assert.Fail("OnStatus Was called");

        sim.UpdateSimInfo(new Info("the same status"));
    }

但是它不能工作-在修复代码以确保事件未被触发之前,测试总是通过。我甚至已经逐步执行了代码,并观察到Assert.Fail()抛出了异常,但这并不会导致测试失败,因为我的事件被包含在try / catch块中,NUnit异常被捕获。

有什么想法吗?


你不能在要测试的代码循环完成时触发一个事件,当你接收到该事件时,确保另一个事件没有被触发吗? - Arwin
3个回答

5
简而言之,你不需要对多线程代码进行单元测试。相反,你需要将其拆分为单线程部分并分别进行测试。

这将在我的测试中留下一个漏洞。我可以单元测试我的Sim对象是否被正确更新 - 肯定有一种模式来测试观察者是否被通知或未被通知,即使它不被称为“单元测试”。 - barry
2
@barry 这个模式很简单:你将多线程移动到另一个模块中(我们称之为 dispatcher),然后测试你的 Sim 是否调用或不调用模拟的 dispatcher(这一切都是同步进行的,所以很容易),并测试真实的 dispatcher 在被调用时是否分派事件。关注点分离在起作用。 - Sergei Rogovtcev
谢谢Serg,听起来很合理。 - barry

0

如果你有一个没有状态的事件会怎么样呢。

[Test]
public void TestOnStatusIsNotFired()
{
    Sim sim = new Sim();
    var mre = new ManualResetEvent(false);
    sim.OnStatus += onStatus => { mre.Set(); Assert.Fail("OnStatus Was called");}
    sim.NoStatus += () => { mre.Set();}

    sim.UpdateSimInfo(new Info("the same status"));
    mre.WaitOne()
}

如果可能的话,我不想在我的应用程序代码中添加事件。此外,这仍然依赖于Assert.Fail()导致测试失败,但实际上并没有失败。 - barry

0

要不要试试这种方式:

mock.Verify(foo => foo.Execute("ping"), Times.Never());

您还可以验证 OnStatus 是否已被调用:

mock.Verify(foo => foo.Execute("ping"), Times.AtLeastOnce());

尝试使用此链接获取许多好的短示例:http://code.google.com/p/moq/wiki/QuickStart


在多线程环境中,这个能可靠地工作吗?我的意思是,verify 什么时候被调用。有可能在我们验证它没有被调用后,onstatus 会被调用吗? - barry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接