一个测试的重构
随着TDD的流行,单元测试越来越成为软件开发的重要组成部分。那么,说到单元测试,大家都会想到NUnit(当然还有CPPUnit等,不过不在本文的讨论范围)。在NUnit中,编写一个测试的Class是很简单的,不需要像JUnit中那样从某个Class继承,这也得益于 DotNet中富有创意的 Attribute 特性。通过Attribute ,便有了不需要从BaseClass继承的 NUnit,便有了不需要使用Xml辅助配置的ORM工具---GroveKit 。在DotNet中,只需要将Class标记TestFixtureAttribute ,这个Class将被NuitFramework发现,并自动调用其中标记有TestAttribute 的方法作为测试方法。所以,在我为一个排序算法编写Test的时候,它大概就是这样的:
using NUnit.Framework;
namespace Sorter.TestCase
{
/**//// <summary>
/// 冒泡算法的测试类
/// </summary>
[TestFixture]
public abstract class TestSortBase
{
protected BubbleSorter sorter ;
[SetUp]
public void SetUp()
{
sorter = new BubbleSorter();
}
[Test]
public void TestIntSort()
{
// some Assert here
}
[Test]
public void TestDoubleSort()
{
// some Assert here
}
[Test]
public void TestDateTimeSort()
{
// some Assert here
}
[Test]
public void TestStringSort()
{
// some Assert here
}
[Test]
[ExpectedException(typeof(ArgumentException))]
public void TestException()
{
// some Assert here
}
}
这个测试类看起来工作的让我满意,在我的算法出现问题的时候,总能被它发现。
冒泡排序是最简单的排序算法,接下来我需要为我的 快速插入排序编写单元测试。首先我想到的是,将上面的类中出现的所有 BubbleSorter 替换为 InsertSorter ,但是很明显,你已经闻到了代码的 Bad Smell了。重复的代码总是让人产生厌恶 。
我试着将BubbleSorter的声明改为SorterBase,将SetUp方法声明为virtual的,然后我让我的InsertSorter从BubbleSorter继承,并重写了SetUp。这似乎解决了问题,但是很明显,InsertSorter与BubbleSorter之间不应该是继承的关系,而应该是兄弟的关系。
怎么办呢?也许你早就想到了,提炼出一个TestBase,让InsertSorter和BubbleSorter从它继承。没错,我提炼了一个抽象类,这在重构名录中大概叫做Extract Super Class,其实,重构就像是设计模式一样,在你知道它是一种经典的重构之前,你已经在大量使用这种方法了。这个TestBase应该是抽象类,因为它也不应该实例化。还有一点,标记为abstract的class,即使被标记TestFixtureAttribute,并且内部含有Test方法,在执行测试的时候,也会被NUnit忽略。但是,为了减轻NUnit的工作量,我还是去除了TestBase的TestFixtureAttribute,这样,代码阅读者也不至于产生误解。
下面是经过重构后的TestBubbleSorter
/// 冒泡排序的测试类
/// </summary>
[TestFixture]
public class TestBubbleSorter : TestSortBase
{
protected override SorterBase CreateSorter()
{
return SorterFactory.GetSorter( typeof( BubbleSorter ) );
}
}
至于SortBase是啥样的,我想地球人都知道了。