快速编写.Net中的单元测试接口

标签: 单元测试 C#
发布时间: 2013/7/1 12:01:55

介绍

我想演示如何快速实现测试接口。它允许我们编写单元测试的接口,只是一次并使用这些测试来测试我们任意数量的接口的实现。


代码 

String Searcher接口  

首先,我们需要确定一个示例接口。这里是一个简单的搜索字符串的接口。示例代码包含一些不同字符串算法实现,这些算法实现来自另一篇文章。

public interface IStringSearcher
{
    /// <summary>
    /// Search for the given pattern
    /// </summary>
    /// <param name="stringToSearch"></param>
    /// <param name="pattern"></param>
    /// <returns></returns>
    IEnumerable<int> SearchString(string stringToSearch, string pattern);
  
    /// <summary>
    /// Search for the given pattern from the starting index (zero-based)
    /// </summary>
    /// <param name="stringToSearch"></param>
    /// <param name="pattern"></param>
    /// <param name="startingIndex"></param>
    /// <returns></returns>
    IEnumerable<int> SearchString(string stringToSearch, string pattern, int startingIndex);
}

然后,我们有一个StringSearcherBase接口的抽象实现,它包含了一些我们字符串搜索算法有需要用到的有用的受保护的方法。

最后,一些算法实现.第一个用String.IndexOf,其他的则用另外一些算法.

/// <summary>
/// Use the .NET method String.IndexOf to do the searching
/// </summary>
public class StringSearcherIndexOf : StringSearcherBase
{
    public override IEnumerable<int> SearchString
        (string stringToSearch, string pattern, int startingIndex)
    {
        int patternLength = pattern.Length;
        int index = startingIndex;
        do
        {
            index = stringToSearch.IndexOf(pattern, index, 
                StringComparison.InvariantCultureIgnoreCase);
            if (index < 0)
                yield break;
            yield return index;
            index += patternLength;
        } 
        while (true);
    }
}
public class StringSearcherBoyerMoore : StringSearcherBase
{
    public override IEnumerable<int> SearchString(string stringToSearch, string pattern, 
        int startingIndex)
    {
        int[] badCharacterShift = BuildBadCharacterShift(pattern);
        int[] suffixes = FindSuffixes(pattern);
        int[] goodSuffixShift = BuildGoodSuffixShift(pattern, suffixes);
  
        int patternLength = pattern.Length;
        int textLength = stringToSearch.Length;
  
        int index = startingIndex;
        while (index <= textLength - patternLength)
        {
            int unmatched;
            for (unmatched = patternLength - 1;
                unmatched >= 0 && 
                    (pattern[unmatched] == stringToSearch[unmatched + index]);
                --unmatched)
                ; // empty
 
            if (unmatched < 0)
            {
                yield return index;
                index += goodSuffixShift[0];
            }
            else
                index += Math.Max(goodSuffixShift[unmatched],
                    badCharacterShift[stringToSearch[unmatched + index]] - 
                        patternLength + 1 + unmatched);
        }
    }
}

编写单元测试 

我们不得不为每一个IStringSearcher接口的实现编写测试,这将耗费巨大的时间.我们想编写一个测试,然后重用他们测试所有的字符串搜索算法.

要做到这一点,我们打算写一个实际上被写入测试的抽象基类.注意抽象方法GetStringSearcherInstance. 

[TestClass]
public abstract class StringSearcherTestBase
{
    /// <summary>
    /// Override this method to implement the tests
    /// </summary>
    /// <returns></returns>
    public abstract IStringSearcher GetStringSearcherInstance();
  
    [TestMethod]
    public void BasicTest()
    {
        IStringSearcher searcher = GetStringSearcherInstance();
        List<int> indexes = searcher.SearchString(
            "Hello. Welcome to unit testing interfaces", 
            "test").ToList();
  
        Assert.AreEqual(1, indexes.Count);
        Assert.AreEqual(23, indexes[0]);
    }
  
    [TestMethod]
    public void NegativeTest()
    {
        IStringSearcher searcher = GetStringSearcherInstance();
        var indexes = searcher.SearchString(
            "Hello. Welcome to unit testing interfaces", 
            "uint").ToList();
  
        Assert.AreEqual(0, indexes.Count);
    }

接着,我们简单执行这个每个字符串算法的测试类.我们想去测试并重写GetStringSearcherInstance方法来返回IStringSearcher类里我们想要的测试.

[TestClass]
public class StringSearcherBoyerMoore_Tests : StringSearcherTestBase
{
    public override IStringSearcher GetStringSearcherInstance()
    {
        return new StringSearcherBoyerMoore();
    }        
}

看测试浏览器,我们看到每个测试在测试资源管理器看,我们可以看到每个测试我们测试的实施。它是那样简单。


而且,如果一些微妙的原因,我想使测试不同的实现之一,那么我可以虚拟一个测试基类,并重写它.

举个例子,如果startingIndex出界,那么所有的字符串搜索类只返回不匹配,但是StringSearcherIndexOf抛出一个异常.因此这个测试在StringSearcherIndexOf_Tests里被覆盖用来测试异常.

[TestClass]
public class StringSearcherIndexOf_Tests : StringSearcherTestBase
{
    public override IStringSearcher GetStringSearcherInstance()
    {
        return new StringSearcherIndexOf();
    }
  
    /// <summary>
    /// Overridden to test an ArgumentOutOfRangeException is thrown
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(ArgumentOutOfRangeException))]
    public override void NegativeTest_StartingIndexOutOfBounds()
    {
        IStringSearcher searcher = GetStringSearcherInstance();
        var indexes = searcher.SearchString(
            "Hello. Welcome to unit testing interfaces", 
            "unit", 
            500).ToList();
    }
}

兴趣点  

以上是使用MSTest的。与任何其他的测试框架,我还没有试过。这将是有趣的,看看其他的单元测试框架是如何处理它。

官方微信
官方QQ群
31647020