你可以通过以下几种方法扩展Nunit:通过custom constraints对我们测试项目引用的Nunit framework进行扩展,针对我们自己的测试项目;通过addin对Nuint Core扩展,这样可以影响Nuint对测试项目的编译运行,此外,还可以对GUI运行工具的扩展。
Custom Constraints (NUnit 2.4 / 2.5)
通过继承抽象类Constraint,你可以实现自定义约束custom constraints 它会一个真实值进行一个测试并且产生适当的提示信息。
public abstract class Constraint { ... public abstract bool Matches( object actual ); public virtual bool Matches( ActualValueDelegate del ); public virtual bool Matches<T>( ref T actual ); public abstract void WriteDescriptionTo( MessageWriter writer ); public virtual void WriteMessageTo( MessageWriter writer ); public virtual void WriteActualValueTo( MessageWriter writer ); ... }
这个类不只是上面所列出的,可供你扩展的包括2个你必须实现的抽象abstract的方法和4个虚拟的方法,它们包含默认实现,你可以根据自己的意思是否去重写它们。继承的子类应该保存用于匹配的真实值在一个受保护的protected的actual字段中以备后用。其中的MessageWriter是一个抽象类,它被TextMessageWriter类实现。查看内置约束的代码,我们可以知道如何去自定义错误信息。
下面是一个Nuint中EmptyStringConstraint类的实现:
/// <summary> /// EmptyStringConstraint 用来测试一个字符串是否为空 /// </summary> public class EmptyStringConstraint : Constraint { /// <summary> /// 测试给定的值是否满足约束 /// </summary> /// <param name="actual">要测试的值</param> /// <returns>测试成功返回True,失败返回false</returns> public override bool Matches(object actual) { // 真实值保存到基类的actual字段中,以备后用 this.actual = actual; if (!(actual is string)) return false; return (string)actual == string.Empty; } /// <summary> /// 向MessageWriter写入约束描述 /// </summary> /// <param name="writer">表现描述的writer</param> public override void WriteDescriptionTo(MessageWriter writer) { writer.Write("<empty>"); } }
自定义约束的语法实现:Nunit本身有一些类包含特定约束的语法实现。当然,Nuint并不会为自定义约束实现语法实现,除非我们自己实现。不过,我们可以用Matches(Constraint) 语法元素来这样写代码:
MyConstraint myConstraint = new MyConstraint(); Assert.That( myArray, Has.Some.Matches(myConstraint) );
Nunit Addins
Nunit最早识别测试是通过继承测试基类和以test开头的方法。但是从2.0开始以特性标记的方式。但是移除了继承子类的机制,我们也失去了一个简单的扩展Nunit内部行为方法。此时,addins填补这个空白,使我们不用修改Nuint本身就可以引入新的行为或修改原来的行为。
Nunit提供了几个扩展点,因为Nunit运行在不同的host和应用不同的应用程序域去运行测试,可以分为三种类型的扩展点:Core,client和gui。也就是我们可以在这三个方面对Nunit进行扩展。
通过插件addin,你可以在任一个扩展点添加多个扩展。每个扩展都必须标明扩展类型,因此扩展host知道如何激发它们:目前的版本中只支持core类型的扩展。
Nunit检查在bin/addins目录(实际上是在和lib同级的addins目录下)下的所有程序集,寻找标记NUnitAddinAttribute并且实现IAddin接口的公共类,然后做为插件加载它们。
NUnitAddinAttribute支持三个可选的命名参数:Type,Name和Description。Name和Description都是字符串形式标明扩展的名字和描述。如果名字没提供,默认使用类名做为名字。Type可以一个或者ExtensionType的组合:
[Flags] public enum ExtensionType { Core=1, Client=2, Gui=4 }
如果类型也没有提供,默认是ExtensionType.Core。
每个插件都必须实现IAddin:
public interface IAddin { bool Install( IExtensionHost host ); }
这个Install方法会被它本身标记类型的host调用,插件应该检查必要的扩展点是否可用然后安装自己,安装成功返回true,失败返回false。这个方法只会被每个扩展host和一个新的测试应用程序域加载时调用一次。
Install方法用IExtensionHost接口定位扩展点:
public interface IExtensionHost { IExtensionPoint[] ExtensionPoints { get; } IExtensionPoint GetExtensionPoint( string name ); ExtensionType ExtensionTypes { get; } }
ExtensionPoints属性返回这些扩展需要的所有扩展点的数组。ExtensionTypes属性返回当前host支持的扩展类型的标识,例如Gui扩展只会被Gui host加载。目前,继承自IExtensionHost的抽象类ExtensionHost实现了这几个方法,也只有CoreExtensions继承自ExtensionHost,因此目前只有Core类型的扩展被支持。
大多数的插件只用到GetExtensionPoint方法去获取一个特定的扩展点的接口,IExtensionPoint定义如下:
public interface IExtensionPoint { string Name { get; } IExtensionHost Host { get; } void Install( object extension ); void Remove( object extension ); }
大多数插件只调用Install方法,它传入一个扩展对象给它要被安装的扩展点。一般情况下,扩展一旦安装不必移除,但是它仍然被提供以防万一。抽象类ExtensionPoint继承此接口并且实现了这几个方法,而它又被SuiteBuilders,TestCaseBuilders,TestDecorators,TestCaseProviders,DataPointProviders,EventListeners这些类继承,而这些就是实际的扩展点。
在2.5中,另一个接口,一个继承自IExtensionPoint的IExtensionPoint2被引入,它允许你设定在同一个扩展点相对于其它扩展调用的顺序:
public interface IExtensionPoint2 : IExtensionPoint { void Install( object extension, int priority ); }
在2.5版本中,只有TestDecorators和DataPointProviders扩展点实现了这个接口。
对于不同的扩展点,这个传入的对象应该继承一个或多个不同的接口。
1,SuiteBuilders (NUnit 2.4)
SuiteBuilder是一个把类构建为测试类的插件。Nunit本身就是利用一个SuiteBuilder识别和构建测试类的。
在插件中我们可以用host通过名字来获取此扩展点对象:
IExtensionPoint suiteBuilders = host.GetExtensionPoint( "SuiteBuilders" );
如果实现对该扩展点扩展,传给Install方法的扩展对象必须实现ISuiteBuilder接口:
public interface ISuiteBuilder { bool CanBuildFrom( Type type ); Test BuildFrom( Type type ); }
CanbuilderFrom应该返回true,如果builder能从指定类型构建测试类,它通常要检查类型和它的特性。BuildFrom应该返回一个包含测试方法的测试类,如果不能构建测试类时返回null。
2,TestCaseBuilders (NUnit 2.4)
TestCaseBuilders是基于方法来创建测试的。Nunit在内部就是用几个TestCaseBuilders来创建各样的测试方法的。
在插件中通过以下代码来获取此扩展点对象:
IExtensionPoint testCaseBuilders = host.GetExtensionPoint( "TestCaseBuilders" );
对该扩展点的扩展类要实现以下两个接口中的一个:
public interface ITestCaseBuilder { bool CanBuildFrom( MethodInfo method ); Test BuildFrom( MethodInfo method ); } public interface ITestCaseBuilder2 : ITestCaseBuilder { bool CanBuildFrom( MethodInfo method, Test suite ); Test BuildFrom( MethodInfo method, Test suite ); }
Nunit会先调用ITestCaseBuilder2 ,如果不合适调用ITestCaseBuilder。
CanBuildFrom应该返回True,如果插件能从提供的方法中构建测试。一些TestCaseBuilder插件只能在应用到特定测试类中的方法中,ITestCaseBuilder2 接口中的suite参数就是用来做这个决定的。BuildFrom方法应该返回传入参数构建的测试方法,如果传入的方法不能用时返回null。
3,TestDecorators (NUnit 2.4)
TestDecorators能对构建生成的测试进行修改。
插件可用以下代码获取该扩展点对象:
IExtensionPoint testDecorators = host.GetExtensionPoint( "TestDecorators" );
传给Install的扩展对象就实现以下接口:
public interface ITestDecorator { Test Decorate( Test test, MemberInfo member ); }
这个Decorate方法可以做:什么也不做,直接返回;修改测试对象的属性,然后返回;舍弃测试对象或把它整合到新的对象中。
根据decorator的需要,它可能要比其它decorator之前或之后运行,decorator可以用重载的Install方法,传入一个优先级标识,这个值从1到9,数值越小,级别越高。以下值的定义,用到的时候可以调用:
4,TestCaseProviders (NUnit 2.5)
TestCaseProviders是和带参的测试一起使用,为带参测试调用时创建测试用例。
插件通过以下代码获取扩展点对象:
IExtensionPoint listeners = host.GetExtensionPoint( "ParameterProviders" );
传给Install方法的对象应实现以下两个接口的一个:
public interface ITestCaseProvider { bool HasTestCasesFor( MethodInfo method ); IEnumerable GetTestCasesFor( MethodInfo method ); } public interface ITestCaseProvider2 : ITestCaseProvider { bool HasTestCasesFor( MethodInfo method, Test suite ); IEnumerable GetTestCasesFor( MethodInfo method, Test suite ); }
如果2不适合时,就调用1接口。
HasTestCasesFor 返回True,如果这个provider对于给定的方法能提供测试用例。如果 provider 只有被应用在特定的测试中,它检查suite参数来决定是返回true或false。
GetParametersFor方法应该返回一系列独立的测试,每个测试表现为一个ParameterSet类型的对象或参数的数组或自定义的包含以下属性的对象:
ParameterSet类也提供这些属性,以供调用。
注意:
a,如果你实现了两个接口,大部分的provider会用其中的一个代理另一个。
b,如果provider要使用测试类的数据,请使用ITestCaseProvider2,以确保它能够调用测试类构造时的参数。
c,从测试类外部获取数据的provider只调用ITestCaseProvider。
d,ITestCaseProvider2在2.5.1版本中才加入的。
5,DataPointProviders (NUnit 2.5)
DataPointProviders以独立方式为带参数的测试方法提供数据。
插件中用以下代码获取此扩展点对象:
IExtensionPoint listeners = host.GetExtensionPoint( "DataPointProviders" );
传给Install方法的扩展对象就实现以下两个接口中的一个:
public interface IDataPointProvider { bool HasDataFor( ParameterInfo parameter ); IEnumerable GetDataFor( ParameterInfo parameter ); } public interface IDataPointProvider2 : IDatapointProvider { bool HasDataFor( ParameterInfo parameter, Test parentSuite ); IEnumerable GetDataFor( ParameterInfo parameter, Test parentSuite ); }
仍然优先采用2.
如果对于指定的参数,provider能提供数据,HasDataFor就返回true。如果只要被特定测试调用,它应该检查提供的参数、关联的方法和提供的parentSuite参数。GetDataFor返回运行测试的一系列独立的数据。
ITestCaseProvider中要注意的几点同样适用于IDataPointProvider。
6,EventListeners (NUnit 2.4.4)
EventListeners用来对测试运行中发生的事件做出反应,通常用来记录信息。这个事件监听调用和测试的运行是异步的,不会对实际的运行产生影响。
插件中可以通过以下方式获取该扩展点对象:
IExtensionPoint listeners = host.GetExtensionPoint( "EventListeners" );
传给Install的扩展对象应实现以下接口:
public interface EventListener { void RunStarted( string name, int testCount ); void RunFinished( TestResult result ); void RunFinished( Exception exception ); void TestStarted(TestName testName); void TestFinished(TestResult result); void SuiteStarted(TestName testName); void SuiteFinished(TestResult result); void UnhandledException( Exception exception ); void TestOutput(TestOutput testOutput); }
你必须提供所有的方法,但可以为空。
扩展的一些建议
首先,Nunit官方文档不建议现在就进行扩展开发,而是希望开发者提倡加入开发或者提出问题和建议给他们。但是,他们仍然列出了一些建议:
1,相对于nunit.core 各个版本中都会有改变,nunit.core.interfaces 是相对稳定的。虽然这两个程序集都还不是定型,但是仅依赖于interfaces的扩展在以后的各个版本中会更易于使用。不幸的是,这样就不能复用Nunit的代码,工作起来会更加困难。现在大部分的插件示例都是针对特定版本的。
2,如果你把一个自定义的特性和你的插件放到了一个程序集,因此用户测试就会依赖这个程序集。如果插件是依赖版本的,那么用户的测试也就依赖版本了。因此,你应该把用户测试需要引用的类放到独立的程序集中,特别是你的扩展依赖nunit.core时。
3,如果使用vs,把任何对nunit.core或nunit.core.interfaces的引用都设成Copy Local为false。如果你也需要自己编译NUnit,这是非常重要的。
4,现在没有方法让decorators按一定的顺序应用。Nunit按反射返回的次序来应用它们,这在不同的运行时是不一样的。
5,不要对已有扩展点做本身要求之外的扩展,相信Nunit以后会提供更易用的扩展点,也可以给Nunit官方提建议。
扩展介绍完毕,下面将列出一个Nunit官方的扩展的例子。