65.9K
CodeProject 正在变化。 阅读更多。
Home

NUnit 测试快速概览

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (16投票s)

2014年1月28日

CPOL

3分钟阅读

viewsIcon

44526

downloadIcon

409

如何使用 NUnit 编写测试代码

引言

在本文中,我们将考虑测试类的不同方法。

使用的环境是

  • NUnit 2.6.2
  • Resharper 运行测试

被测试代码

public sealed class Calculator : ICalculator
{
    public int Divide(int a, int b)
    {
        return a/b;
    }
}

在这个项目中,我们将测试计算器的三个测试用例

  • 1/1 = 1
  • 2/1 = 2
  • 1/0 无效

简单解决方案

[TestFixture]
public sealed class CalculatorTest1
{
    private Calculator _calculator;
 
    [SetUp]
    public void Setup()
    {
        _calculator = new Calculator();
    }
 
    [Test]
    public void OneDividedByOne()
    {
        int result = _calculator.Divide(1, 1);
        Assert.AreEqual(1, result);
    }
 
    [Test]
    public void TwoDividedByOne()
    {
        int result = _calculator.Divide(2, 1);
        Assert.AreEqual(2, result);
    }
 
    [Test]
    public void OneDividedByZero()
    {
        Assert.Throws<DivideByZeroException>(() => _calculator.Divide(1, 0));
    }
}

这是你在阅读 NUnit 文档之前可能编写的简单解决方案。

让我们尝试让它更好一些。

使用 TestCase 属性

[TestFixture]
public sealed class CalculatorTest2
{
    [SetUp]
    public void Setup()
    {
        _calculator = new Calculator();
    }

    private Calculator _calculator;

    [TestCase(1, 1, ExpectedResult = 1, TestName = "OneDividedByOne")]
    [TestCase(2, 1, ExpectedResult = 2, TestName = "TwoDividedByOne")]
    [TestCase(1, 0, ExpectedResult = 0, ExpectedException = typeof (DivideByZeroException), TestName = "OneDividedByZero")]
    public int CalculatorTestMethod(int firstNumber, int secondNumer)
    {
        return _calculator.Divide(firstNumber, secondNumer);
    }
} 

使用 TestCase 属性使代码更短,将所有情况分组在一个方法中。

这种编写测试的方法似乎非常适合测试我们的 Calculator

添加新的 TestCase 非常快速和清晰。

但是,如果您的被测试方法将引用类型作为参数,而不是字符串,则不能使用 TestCase 属性。

使用 TestCaseSource 属性

[TestFixture]
public sealed class CalculatorTest3
{
    private Calculator _calculator;
 
    [SetUp]
    public void Setup()
    {
        _calculator = new Calculator();
    }
 
    [TestCaseSource(typeof(CalculatorTest3TestCaseDataFactory), "TestCases")]
    public void CalculatorTestMethod(int firstNumber, int secondNumer, int expectedResult)
    {
        var result = _calculator.Divide(firstNumber, secondNumer);
        Assert.AreEqual(expectedResult, result,"A meaning description to help if test crashes");
    }
}

以及关联的测试用例数据工厂

public class CalculatorTest3TestCaseDataFactory
{
    public static IEnumerable TestCases
    {
        get
        {
            yield return new TestCaseData(1, 1, 1).SetName("OneDividedByOne");
            yield return new TestCaseData(2, 1, 2).SetName("TwoDividedByOne");
            yield return new TestCaseData(1, 0, default(int))
                .Throws(typeof(DivideByZeroException))
                .SetName("OneDividedByZero");
        }
    }
}

此实现允许划分关注点

  • CalculatorTest3 包含测试数据的方式
  • CalculatorTest3TestCaseDataFactory 提供数据

在我们的简单示例中,我们必须使用 Returns() 方法来设置我们的预期结果。

让我告诉你为什么在我被测试的类返回一个引用类型 MyClass 时,我不会使用它,我编写了

  • NUnit 将使用 MyClass.Equals,也许我想以不同的方式比较我的对象
  • 我可以在 TestClass 中编码我自己的比较,添加有意义的描述

输入我们的数据

让我们声明 CalculatorTestCaseData,它将保存我们的类型化数据

public class CalculatorTestCaseData
{
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public int ExpectedResult { get; set; }
}

现在测试类将是

[TestFixture]
public sealed class CalculatorTest4
{
    private Calculator _calculator;

    [SetUp]
    public void Setup()
    {
        _calculator = new Calculator();
    }

    [TestCaseSource(typeof(CalculatorTestCaseDataFactory4), "GetTestCases")]
    public void CalculatorTestMethod(CalculatorTestCaseData testCase)
    {
        int result = _calculator.Divide(testCase.FirstNumber, testCase.SecondNumber);
        Assert.AreEqual(testCase.ExpectedResult, result);
    }
}

以及将实例化 CalculatorTestCaseData 的工厂

public class CalculatorTestCaseDataFactory4
{
    public IEnumerable GetTestCases
    {
        get
        {
            yield return new TestCaseData(OneDividedByOne()).SetName("OneDividedByOne");
            yield return new TestCaseData(TwoDividedByOne()).SetName("TwoDividedByOne");
            yield return new TestCaseData(OneDividedByZero())
                .Throws(typeof(DivideByZeroException))
                .SetName("OneDividedByZero");
        }
    }

    private CalculatorTestCaseData OneDividedByOne()
    {
        return new CalculatorTestCaseData
            {
                FirstNumber = 1,
                SecondNumber = 1,
                ExpectedResult = 1
            };
    }

    private CalculatorTestCaseData TwoDividedByOne()
    {
        return new CalculatorTestCaseData
            {
                FirstNumber = 2,
                SecondNumber = 1,
                ExpectedResult = 2
            };
    }

    private CalculatorTestCaseData OneDividedByZero()
    {
        return new CalculatorTestCaseData
            {
                FirstNumber = 1,
                SecondNumber = 0
            };
    }
}

我们现在正在操作类型化的数据,测试用例比之前的示例更有意义。

此示例中的一个缺点是测试用例名称和方法名称相同,不幸的是它不是重构证明的(如果您想更改名称,则必须更改每个名称)。

让我们用下一个例子来解决它。

使其成为重构证明

[TestFixture]
public sealed class CalculatorTest5
{
    private Calculator _calculator;

    [SetUp]
    public void Setup()
    {
        _calculator = new Calculator();
    }

    [TestCaseSource(typeof(CalculatorTestCaseDataFactory5), "GetTestCases")]
    public void CalculatorTestMethod(CalculatorTestCaseData testCase)
    {
        int result = _calculator.Divide(testCase.FirstNumber, testCase.SecondNumber);
        Assert.AreEqual(testCase.ExpectedResult, result);
    }
}

使用数据工厂

public class CalculatorTestCaseDataFactory5
{
    private static readonly TestCaseDataFactory<CalculatorTestCaseData> TestCaseDataFactory = new TestCaseDataFactory<CalculatorTestCaseData>();

    public IEnumerable GetTestCases
    {
        get
        {
            yield return OneDividedByOne();
            yield return TwoDividedByOne();
            yield return OneDividedByZero().Throws(typeof(DivideByZeroException));
        }
    }

    private TestCaseData OneDividedByOne()
    {
        var calculatorTCD = new CalculatorTestCaseData
        {
            FirstNumber = 1,
            SecondNumber = 1,
            ExpectedResult = 1
        };
        return TestCaseDataFactory.Get(calculatorTCD);
    }

    private TestCaseData TwoDividedByOne()
    {
        var calculatorTCD = new CalculatorTestCaseData
        {
            FirstNumber = 2,
            SecondNumber = 1,
            ExpectedResult = 2
        };
        return TestCaseDataFactory.Get(calculatorTCD);
    }

    private TestCaseData OneDividedByZero()
    {
        var calculatorTCD = new CalculatorTestCaseData
        {
            FirstNumber = 1,
            SecondNumber = 0
        };
        return TestCaseDataFactory.Get(calculatorTCD);
    }
}

TestCaseDataFactory

public sealed class TestCaseDataFactory<T>
{
    public TestCaseData Get(T data, [CallerMemberName] string memberName = "noName")
    {
        return new TestCaseData(data).SetName(memberName);
    }
}

我们获得了之前版本的所有优点,而没有名称的重复。 您将找到附加的 Resharper 模板,用于制作此版本的测试。

通过 TestFixture 传递参数

现在让我们想象一下,我们有另一个 ICalculator 的实现,它对于相同的输入工作方式完全相同: SlowCalculator 。 我们将不得不使用相同的测试用例(前一部分的测试用例)来测试它。 一种干净的方法是将参数传递给 TestFixture 属性,如下所示。

[TestFixture(CalculatorType.Standard)]
[TestFixture(CalculatorType.Slow)]
public sealed class CalculatorTest6
{
    private ICalculator _calculator;
    private readonly CalculatorType _calculatorType;
    private readonly CalculatorFactory _calculatorFactory;

    public CalculatorTest6(CalculatorType calculatorType)
    {
        _calculatorType = calculatorType;
        _calculatorFactory = new CalculatorFactory();
    }

    [SetUp]
    public void Setup()
    {
        _calculator = _calculatorFactory.Get(_calculatorType);
    }

    [TestCaseSource(typeof(CalculatorTestCaseDataFactory5), "GetTestCases")]
    public void CalculatorTestMethod(CalculatorTestCaseData testCase)
    {
        int result = _calculator.Divide(testCase.FirstNumber, testCase.SecondNumber);
        Assert.AreEqual(testCase.ExpectedResult, result);
    }
}
这是 CalculatorFactory
public class CalculatorFactory
{
    public ICalculator Get(CalculatorType calculatorType)
    {
        switch (calculatorType)
        {
            case CalculatorType.Standard: return new Calculator();
            case CalculatorType.Slow: return new SlowCalculator();
        }

        string message = String.Format("No implementation matching for type {0}. Please add it", calculatorType);
        throw new ArgumentException(message);
    }
}

三个测试用例将应用于正常的 Calculator,然后应用于我们的新 SlowCalculator

我们的测试执行的输出

结论

我们已经看到了使用 NUnit 编码单元测试的各种版本。

关于你必须测试的类,你将不得不选择合适的方式。 使用 Calculator 示例,使用 TestCase 属性的那个似乎很有效且更简单。

在更困难的示例中,TestCaseSource 可以帮助你获得适应的代码覆盖率。

所以现在你没有更多的借口来跳过编码测试,冲啊冲啊冲。

© . All rights reserved.