在选择单元测试框架时,可以从以下几个方面去考虑:
常用的C#单元测试框架有xUnit.Net, NUnit, MSTest(Visual Studio),以下主要介绍NUnit测试框架。
NUnit是一个开源的单元测试框架,用于.NET framework和Mono框架下进行的单元测试。与JUnit在Java中的作用相同,是xUnit家族中的一员。
NUnit框架安装有以下几种方式:
以下介绍前两种安装方法:
找到并选择NUnit包,在右侧勾选要添加的项目(测试项目,通常为dll),点击安装。Visual Studio会将包下载到解决方案所在目录的packages文件夹下,并在勾选的项目中引用NUNit framework;
NUnit单元测试可以使用NUnit框架自带的runner运行,也可以集成到Visual Studio的测试平台上,使用Microsoft提供的的runner运行。这两种方式需要安装不同的NuGet包:
使用NUnit框架自带的runner:在NuGet包管理器中搜索NUnit.Console,并安装。安装完之后,就可以在packages\NUnit.ConsoleRunner.3.X.X\tools中找到unit3-console并运行;
使用Microsoft提供的的runner:在NuGet包管理器中搜索NUnit3TestAdapter,并安装;
确认测试项目引用了nunit.framework。
NUnitLite是一种轻量化的安装方式,不用安装NUnit runner和test engine assemblies。仅需要安装NUnit framework和一个小型的runner即可。
public static int Main(string[] args)
{
return new AutoRun().Execute(args);
}
[TestFixture]
标识一个测试类
[Test]
标识一个测试方法(对于非参数化的测试,为一个测试用例)
[TestFixture]
public class SuccessTests
{
// A simple test
[Test]
public void Add()
{ /* ... */ }
}
[TestCase]
在参数化测试中,标识一个测试用例(可以包含输入和预期数据)
[TestCase(12, 3, 4)]
[TestCase(12, 2, 6)]
[TestCase(12, 4, 3)]
public void DivideTest(int n, int d, int q)
{
Assert.AreEqual(q, n / d);
}
[TestCase(12, 3, ExpectedResult=4)]
[TestCase(12, 2, ExpectedResult=6)]
[TestCase(12, 4, ExpectedResult=3)]
public int DivideTest(int n, int d)
{
return n / d;
}
[TestCaseSource]
在参数化测试中,标识一个测试用例的参数来源
public class MyTestClass
{
[TestCaseSource(nameof(DivideCases))]
public void DivideTest(int n, int d, int q)
{
Assert.AreEqual(q, n / d);
}
static object[] DivideCases =
{
new object[] { 12, 3, 4 },
new object[] { 12, 2, 6 },
new object[] { 12, 4, 3 }
};
}
public class MyTestClass
{
[TestCaseSource(typeof(DivideCases))]
public void DivideTest(int n, int d, int q)
{
Assert.AreEqual(q, n / d);
}
}
class DivideCases : IEnumerable
{
public IEnumerator GetEnumerator()
{
yield return new object[] { 12, 3, 4 };
yield return new object[] { 12, 2, 6 };
yield return new object[] { 12, 4, 3 };
}
}
[TestFixtureSource]
在参数化测试中,为一个测试脚手架设置参数来源,通过构造函数的参数传递
[TestFixtureSource(nameof(FixtureArgs))]
public class MyTestClass
{
public MyTestClass(string word, int num) { ... }
/* ... */
static object [] FixtureArgs = {
new object[] { "Question", 1 },
new object[] { "Answer", 42 }
};
}
[Setup]
在一个TestFixture中,提供运行测试用例之前的统一设置,每次运行一个测试用例之前就会调用一次。
[TearDown]
在一个TestFixture中,提供运行测试用例之后的统一操作,每次运行一个测试用例之后就会调用一次。
namespace NUnit.Tests
{
using System;
using NUnit.Framework;
[TestFixture]
public class SuccessTests
{
[SetUp] public void Init()
{ /* ... */ }
[TearDown] public void Cleanup()
{ /* ... */ }
[Test] public void Add()
{ /* ... */ }
}
}
[OneTimeSetUp]
在一个TestFixture中,提供运行测试用例之前的统一设置,运行TestFixture中任意一个测试用例之前会调用一次。
[OneTimeTearDown]
在一个TestFixture中,提供运行测试用例之后的统一操作,运行TestFixture中任意一个测试用例之后会调用一次。
[Ignore]
标识一个测试类或方法由于某些原因不应执行,在NUnit3框架中,必须指明原因。
namespace NUnit.Tests
{
using System;
using NUnit.Framework;
[TestFixture]
[Ignore("Ignore a fixture")]
public class SuccessTests
{
// ...
}
}
NUnit使用两种断言系统,约束模型(Constraint Model)和经典模型(Classic Model)。
经典模型对于每一种断言都有单独的一个方法,例如:
StringAssert.AreEqualIgnoringCase("Hello", myString);
Assert类提供一些非常常用的断言方法:
Assert.True
Assert.False
Assert.Null
Assert.NotNull
Assert.Zero
Assert.NotZero
Assert.IsNaN
Assert.IsEmpty
Assert.IsNotEmpty
Assert.AreEqual
Assert.AreNotEqual
Assert.AreSame
Assert.AreNotSame
Assert.Contains
Assert.Greater
Assert.GreaterOrEqual
Assert.Less
Assert.LessOrEqual
除此之外还有以下几个类也提供相应的断言方法:
约束模型只有Assert.That()一种方法,参数是约束对象。根据不同的断言类型有不同的约束对象,形成一系列重载函数。以下两个断言形式是等价的:
Assert.That(myString, Is.EqualTo("Hello"));
Assert.That(myString, new EqualConstraint("Hello"));
目前NUnit框架仍然支持旧的经典模型,但是已不添加新功能,新功能都是使用约束模型实现的。因此应尽可能的使用约束模型。有些断言形式只能使用约束模型,如:
int[] array = new int[] { 1, 2, 3 };
Assert.That(array, Has.Exactly(1).EqualTo(3));
Assert.That(array, Has.Exactly(2).GreaterThan(1));
Assert.That(array, Has.Exactly(3).LessThan(100));
而本质上经典模型全部是使用约束模型实现的,也就是说所有的经典模型的断言形式都能找到对应的约束模型的形式,比如以下两个断言是等价的:
Assert.AreEqual(4, 2+2);
Assert.That(2+2, Is.EqualTo(4));
一般来说如果一个函数包含多个断言,一个断言失败了,后续的断言(如果有的话)不会执行,因此,一般一个测试仅会有一个断言。但是某些情况,比如一个UI界面样式的测试可能需要在一个测试方法里添加多个断言,此时可以使用Assert.Multiple方法:
[Test]
public void ComplexNumberTest()
{
ComplexNumber result = SomeCalculation();
Assert.Multiple(() =>
{
Assert.AreEqual(5.2, result.RealPart, "Real part");
Assert.AreEqual(3.9, result.ImaginaryPart, "Imaginary part");
});
}
此时,如果代码块中的第一个断言失败了,测试不会马上终止,而是继续执行第二个断言,最终将结果统一返回。
参考: