使用事件和委托进行单元测试的类

7

我希望你能帮助我进行测试。

我有以下类

public delegate void OnInvalidEntryMethod(ITnEntry entry, string message);

public class EntryValidator
{
    public event OnInvalidEntryMethod OnInvalidEntry;

    public bool IsValidEntry(ITnEntry entry, string ticker)
    {
        if (!IsFieldValid(entry, ticker.Trim().Length.ToString(), "0"))
            return false;

        return true;
    }

    private bool IsFieldValid(ITnEntry entry, string actual, string invalidValue)
    {
        if (actual == invalidValue)
        {
            RaiseInvalidEntryEvent(entry);
            return false;
        }

        return true;
    }

    private void RaiseInvalidEntryEvent(ITnEntry entry)
    {
        if (OnInvalidEntry != null)
            OnInvalidEntry(entry, "Invalid entry in list: " + entry.List.Name + ".");
    }
}

我目前已经编写了测试用例,但在处理下面的事件和委托方面遇到了困难。
[TestFixture]
public class EntryValidatorTests
{
    private EntryValidator _entryValidator;

    private FakeTnEntry _selectedEntry;
    private string _ticker;

    [SetUp]
    public void Setup()
    {
        _entryValidator = new EntryValidator();
        _ticker = "BOL";
    }

    private FakeTnEntry MakeEntry(string ticker)
    {
        return new FakeTnEntry { Ticker = ticker};
    }

    [Test]
    public void IsValidEntry_WithValidValues()
    {
        _selectedEntry = MakeEntry(_ticker);

        Assert.IsTrue(_entryValidator.IsValidEntry(_selectedEntry, _selectedEntry.Ticker));
    }

    [Test]
    public void IsValidEntry_WithInValidTicker()
    {
        _selectedEntry = MakeEntry("");
        Assert.IsFalse(_entryValidator.IsValidEntry(_selectedEntry, _selectedEntry.Ticker));
    }
}}

请问是否有人可以帮忙?谢谢。
3个回答

12

最简单的方法可能就是使用匿名方法订阅事件:

[Test]
public void IsValidEntry_WithValidValues()
{
    _selectedEntry = MakeEntry(_ticker);
    _entryValidator.OnInvalidEntry += delegate { 
        Assert.Fail("Shouldn't be called");
    };

    Assert.IsTrue(_entryValidator.IsValidEntry(_selectedEntry, _selectedEntry.Ticker));
}    

[Test]
public void IsValidEntry_WithInValidTicker()
{
    bool eventRaised = false;
    _selectedEntry = MakeEntry("");
    _entryValidator.OnInvalidEntry += delegate { eventRaised = true; };

    Assert.IsFalse(_entryValidator.IsValidEntry(_selectedEntry, _selectedEntry.Ticker));
    Assert.IsTrue(eventRaised);
}

在第二个测试中,你可能还想验证事件参数是否符合预期。
另外请注意,“invalid”是一个单词——因此你的测试应该是IsValidEntry_WithInvalidTicker。我也不会费心去设置——我只会在每个测试中声明新的本地变量。

非常感谢您的快速回复,先生。我需要为OnInvalidEntry创建一个属性或事件吗? - user175084
@user175084: 哪个位?匿名方法?你熟悉Lambda表达式吗? - Jon Skeet
@user175084:这里没有特定于测试的内容。您只是使用匿名方法订阅事件,这有点像lambda表达式。(通常我会使用lambda表达式,但当您不关心参数时,匿名方法稍微简单一些。)请参见http://msdn.microsoft.com/en-us/library/0yw3tz5k.aspx。 - Jon Skeet
我可能会挖出一个旧的帖子,但我处于类似的位置 - 对事件驱动的内容进行单元测试。现在这个解决方案的问题是,如果您的代码在触发事件之前需要一段时间(执行一些复杂处理),则可能会在实际触发事件之前(即使只有一瞬间)就到达“Assert.IsTrue(eventRaised)”这一步。然后,即使您的代码正确工作,测试也会失败。如何避免这种情况? - Daniel Gruszczyk
@DanielGruszczyk:除非你在后台线程中某种方式引发了事件,否则不应该出现这种情况...但你必须显式地做这件事。 - Jon Skeet
显示剩余3条评论

2
我建议你重构你的类,将RaiseInvalidEntryEvent方法改为虚方法,这样在IsValidEntry_WithInValidTicker测试中就可以使用Mock进行模拟,并且验证当票据无效时是否调用了该方法。
然后,我会再写一个测试来验证RaiseInvalidEntryEvent方法单独调用了匿名委托。
单元测试应该尽可能地原子化,因此你需要在不同的测试中验证这两种行为。
public delegate void OnInvalidEntryMethod(ITnEntry entry, string message);

public class EntryValidator
{
    public event OnInvalidEntryMethod OnInvalidEntry;

    public bool IsValidEntry(ITnEntry entry, string ticker)
    {
        if (!IsFieldValid(entry, ticker.Trim().Length.ToString(), "0"))
            return false;

        return true;
    }

    private bool IsFieldValid(ITnEntry entry, string actual, string invalidValue)
    {
        if (actual == invalidValue)
        {
            RaiseInvalidEntryEvent(entry);
            return false;
        }

        return true;
    }

    public virtual void RaiseInvalidEntryEvent(ITnEntry entry)
    {
        if (OnInvalidEntry != null)
            OnInvalidEntry(entry, "Invalid entry in list: " + entry.List.Name + ".");
    }
}

// Had to reverse engineer the following since they were not available in the question
public interface ITnEntry
{
    Ticket List { get; set; }
    string Ticker { get; set; }
}

public class TnEntry : ITnEntry
{
    public Ticket List { get; set; }
    public string Ticker { get; set; }
}

public class Ticket
{
    public string Name { get; set; }
}

注意:一些面向对象编程(OOP)的倡导者会对公共声明而不是私有声明感到不满,基本上单元测试和TDD有一些要求与纯OOP相矛盾。为了简单起见,我已经将RaiseInvalidEntryEvent设置为public,但通常我会将其设置为internal,然后通过InternalsVisibleTo将程序集暴露给单元测试。我过去4年一直在进行TDD,很少再使用private。
而且,这些单元测试将很快(注意,这是使用VS2012中的MSTEST框架):
[TestClass]
public class UnitTest1
{
    #region TestHelpers

    private ITnEntry MakeEntry(string ticker)
    {
        return new TnEntry {Ticker = ticker, List = new Ticket()};
    }

    #endregion

    [TestMethod]
    public void IsValidEntry_WithValidValues_ReturnsTrue()
    {
        // ARRANGE
        var target = new EntryValidator();
        var selectedEntry = MakeEntry("BOL");

        // ACT
        bool actual = target.IsValidEntry(selectedEntry, selectedEntry.Ticker);

        // ASSERT
        Assert.IsTrue(actual);
    }

    [TestMethod]
    public void IsValidEntry_WithInValidTicker_ReturnsFalse()
    {
        // ARRANGE
        var target = new EntryValidator();
        var selectedEntry = MakeEntry("");

        // ACT
        bool actual = target.IsValidEntry(selectedEntry, selectedEntry.Ticker);

        // ASSERT
        Assert.IsFalse(actual);
    }

    [TestMethod]        
    public void IsValidEntry_WithInvalidTicker_RaisesEvent()
    {
        // ARRANGE
        // generate a dynamic mock which will stub all virtual methods
        var target = Rhino.Mocks.MockRepository.GenerateMock<EntryValidator>();
        var selectedEntry = MakeEntry("");

        // ACT
        bool actual = target.IsValidEntry(selectedEntry, selectedEntry.Ticker);

        // ASSERT
        // assert that RaiseInvalidEntryEvent was called
        target.AssertWasCalled(x => x.RaiseInvalidEntryEvent(Arg<ITnEntry>.Is.Anything));
    }

    [TestMethod]
    public void RaiseInvalidEntryEvent_WithValidHandler_CallsDelegate()
    {
        // ARRANGE
        var target = new EntryValidator();
        var selectedEntry = MakeEntry("");
        bool delegateCalled = false;

        // attach a handler to set delegateCalled to true
        target.OnInvalidEntry += delegate 
        {
            delegateCalled = true;
        };

        // ACT
        target.IsValidEntry(selectedEntry, selectedEntry.Ticker);

        // ASSERT
        Assert.IsTrue(delegateCalled);
    }
}

1
您的测试应该订阅事件OnInvalidEntry,使用虚拟方法调用IsValidEntry并检查结果。

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