单元测试框架NUnit 之 Attributes特性(三)
29,TestFixtureAttribute (NUnit 2.0 / 2.5)
它可以标志一个类是测试类,可以包含测试方法、setup和teardown方法。
从2.5.3以后,这个类可以是public, protected, private 或者 internal,在.net2.0之后可以是静态的,也可以是泛型类,也可以是abstract。
如果TestFixtureAttribute构造时不带参数,测试类必须有一个默认的构造函数;如果带参数,必须有对应的构造函数。
如果不符合要求,这个类将是不可运行并且会报告一个错误。因为Nunit可能会一个会话中多次创建这个类,因此这个类的构造函数不应该有任何负作用。
从2.5开始,TestFixture特性对于非泛型的类或没有带参构造函数的类的不是必须的。只要它包含一个标记有Test,TestCase 或 TestCaseSource特性的就去,它仍然会被当做测试类对待。
TestFixtureAttribute可以被应用到基类上,然后被其它类继承,当然这个基类也可以是abstract的。如果子类应该有一个不同于基类的类型参数或构造,这时将按以下规则采用忽略掉一些多余的testfixtureattribute:如果所有TestFixture提供了构造参数或类型参数,它们都会被使用;如果一些提供参数而其它没有,则带参数的会被使用,其它的会被忽略;如果都没有提供参数,则Nunit会随便选择一个,这是不能预测的,因此这种情况应该避免。这些都是为了让泛型或多个构造的类正常的工作。
[TestFixture] public class AbstractFixtureBase { ... } [TestFixture(typeof(string))] public class DerivedFixture<T> : AbstractFixtureBase { ... }
如果基类的构造没被忽略的话,这会造成一个错误。
从2.5开始,testfixture的构造可以带参数,它们以testfixtureattribute的参数出现,Nuint会把用这些参数调用对应的构造函数创建测试类的实例。你可通过设定命名参数Ignore 为true 或IgnoreReason 为一个非空的字符串,来让Nunit忽略这个实例的运行,还可以指定Category设定分类。
[TestFixture("hello", "hello", "goodbye")] [TestFixture("zip", "zip")] [TestFixture(42, 42, 99)] public class ParameterizedTestFixture { private string eq1; private string eq2; private string neq; public ParameterizedTestFixture(string eq1, string eq2, string neq) { this.eq1 = eq1; this.eq2 = eq2; this.neq = neq; } public ParameterizedTestFixture(string eq1, string eq2) : this(eq1, eq2, null) { } public ParameterizedTestFixture(int eq1, int eq2, int neq) { this.eq1 = eq1.ToString(); this.eq2 = eq2.ToString(); this.neq = neq.ToString(); } [Test] public void TestEquality() { Assert.AreEqual(eq1, eq2); if (eq1 != null && eq2 != null) Assert.AreEqual(eq1.GetHashCode(), eq2.GetHashCode()); } [Test] public void TestInequality() { Assert.AreNotEqual(eq1, neq); if (eq1 != null && neq != null) Assert.AreNotEqual(eq1.GetHashCode(), neq.GetHashCode()); } }
这个类会被按照testfixture指定的参数实例化三次,我们可以看到这个类有三个对应的构造函数。
从2.5开始,我们可以使用泛型类为测试类。为了实例化泛型类,你应该在TestFixtureAttribute中指定类型或者通过命名参数TypeArgs来指定。
[TestFixture(typeof(ArrayList))] [TestFixture(typeof(List<int>))] public class IList_Tests<TList> where TList : IList, new() { private IList list; [SetUp] public void CreateList() { this.list = new TList(); } [Test] public void CanAddToList() { list.Add(1); list.Add(2); list.Add(3); Assert.AreEqual(3, list.Count); } }
这个类会被实例化两次,其中类型化参数会被ArrayList和List<int>来替换。
如果一个泛型测试类,并且有多个构造函数,有以下三种方法来实例它:
1.都作为TestFixtureAttribute的参数,其中类型化参数以Type类型出现,其它参数作为构造函数的参数。
[TestFixture(typeof(double), typeof(int), 100.0, 42)] [TestFixture(typeof(int) typeof(double), 42, 100.0)] public class SpecifyBothSetsOfArgs<T1, T2> { T1 t1; T2 t2; public SpecifyBothSetsOfArgs(T1 t1, T2 t2) { this.t1 = t1; this.t2 = t2; } [TestCase(5, 7)] public void TestMyArgTypes(T1 t1, T2 t2) { Assert.That(t1, Is.TypeOf<T1>()); Assert.That(t2, Is.TypeOf<T2>()); } }
2.用命名参数TypeArgs来表示类型参数。如上面的例子可用
[TestFixture(100.0, 42, TypeArgs=new Type[] {typeof(double), typeof(int) } )] [TestFixture(42, 100.0, TypeArgs=new Type[] {typeof(int), typeof(double) } )]
3.在某些情况下,类型参数可以从给定的参数的类型推断
[TestFixture(100.0, 42)] [TestFixture(42, 100.0)] public class DeduceTypeArgsFromArgs<T1, T2> { T1 t1; T2 t2; public DeduceTypeArgsFromArgs(T1 t1, T2 t2) { this.t1 = t1; this.t2 = t2; } [TestCase(5, 7)] public void TestMyArgTypes(T1 t1, T2 t2) { Assert.That(t1, Is.TypeOf<T1>()); Assert.That(t2, Is.TypeOf<T2>()); } }
30,TestFixtureSetUpAttribute (NUnit 2.1 / 2.5)
这个特性应用在测试类内部,提供在该类的所有测试执行之前只执行一次的功能。在2.5之前,在一个类内部只能应用在实例方法上,并且只能应用一次,而在2.5之后,可以应用到实例或静态方法上,并且可以应用多次,但是通常我们在一个级别继承层次上只用一次。如果这个方法出错或抛出异常,这个类任何测试都不会被运行。
namespace NUnit.Tests { using System; using NUnit.Framework; [TestFixture] public class SuccessTests { [TestFixtureSetUp] public void Init() { /* ... */ } [TestFixtureTearDown] public void Cleanup() { /* ... */ } [Test] public void Add() { /* ... */ } } }
31,TestFixtureTearDownAttribute (NUnit 2.1 / 2.5)
这个特性也是就用在测试类内部,这指定某个方法在这个类的所有测试运行完之后执行一次。用法和TestFixtureSetUpAttribute方法一致。
32,TimeoutAttribute (NUnit 2.5)
它指定一个测试运行的超时时间,以毫秒为单位。如果测试运行时间超时,执行会被中断,测试失败并报告消息指出运行超时。它也可以应用在程序集或类上,为程序集或类的所有测试方法指定一个默认的超时时间。
从2.5.5开始,当用调试器运行时超时时间不起作用。
33,ValuesAttribute (NUnit 2.5)
这个特性为带参数的测试方法的一个参数提供一组值。Nunit按一定的方法使用这些值来生成测试用例。
[Test] public void MyTest( [Values(1,2,3)] int x, [Values("A","B")] string s) { ... }
会按以下的方式运行6次:
MyTest(1, "A") MyTest(1, "B") MyTest(2, "A") MyTest(2, "B") MyTest(3, "A") MyTest(3, "B")
34,ValueSourceAttribute (NUnit 2.5)
和ValuesAttribute 一样也是为带参数的测试方法的一个参数提供一组值,不过它和TestCaseSourceAttribute指定的是一个数据源,而不是直接提供数值。用法参照TestCaseSourceAttribute。
下面是两个该版本中试验中的特性:
35,DatapointAttribute / DatapointsAttribute (NUnit 2.5) (试验)
Datapoint 和 Datapoints 特性是用来为下面会提到的Theories attribute来提供数据的。其它的测试都会忽略这个特性。
Datapoint :用来标记字段的。当一个theory测试加载时,nuint会用和参数类型相同的并且标记datapoint特性字段提供的数据来做为实际的参数。我们应该注意的是,只有标记datapoint,数据类型和theory方法参数一致的字段才会被使用。
Datapoints :除了指定单独的数据源,我们可以用这个特性来指定集合。可以用来标记字段、属性和方法:要求必须的需求类型的数组或返回需求类型枚举的IEnumerable<T> 类型。
自动提供的数据源:通常为boolean或枚举类型的参数指定datapoints不是必需的,因为Nuint会按所有可能的值生成测试用例。
如
[Theory] public void IsSqur(bool fm)
这样的测试方法,不用指定数据源,Nunit会自动的生成IsSqur(true)和IsSqur(false)来检测测试。
但是从2.5.4之后,你可以指定ture\false给boolean类型的参数或提供一个枚举值。因此,当你并不希望应用所有可能的值,你可以通过提供自己的datapoints来覆盖这种行为。如果你为参数应用任何datapoints,自动提供的数据源将被忽略。
36,TheoryAttribute (NUnit 2.5) (试验)
这个标记一个特殊的测试,它用来检查开发中的系统的一般语义:通常的测试都是举例的,开发人员会在测试代码中提供一个或多个输入值和期望的输出值来检验要测试的代码,而theory,确认的是否所有满足设想Assume条件的参数的断言都会成功。
被标记的方法必须有参数,初看上去和之前提到的带参数的测试很相似,但是它需要一个额外的数据源并且允许对这些数据进行设想Assume的处理。因此,主要的区别就是theory提供更通用而不只是指定的一组数据的测试。
theory的主要数据源就是datapoint/datapoints标记的数据,正像前面说的,Nuint会应用与theory方法参数类型匹配的上述数据源。除此之外,你还可以之前提到的提供一系列数据的数据源。但是,这不应该过度使用,这与我们之前所说的theory和举例测试两者之前的区别是背道而驰的。
theory本身可以可以确保提供的数据符合它的设想:Assume.That(...),用法类似ssert.That(...),但是不会造成测试失败。如果这个设想不满足,将会返回一个不确定的结果,不是成功也不是失败。
theory的结果是采用一系列测试用例的结果:如果所有测试用例的设想都不符合,将会失败;如果其中的任何一个断言失败,也是失败;如果有一些用例通过了设想,并且没有断言失败或异常,结果是成功。
例子:
public class SqrtTests { [Datapoints] public double[] values = new double[] { 0.0, 1.0, -1.0, 42.0 }; [Theory] public void SquareRootDefinition(double num) { Assume.That(num >= 0.0); double sqrt = Math.Sqrt(num); Assert.That(sqrt >= 0.0); Assert.That(sqrt * sqrt, Is.EqualTo(num).Within(0.000001)); } }
上面的例子:为了验证给定一个非负的数,它的平方根也是非负的,平方根执行平方操作会是原来的数。
接下来将介绍nuint扩展。