首先定义一个自定义的attribute
using System; namespace NUnit.Core.Extensions { /// <summary> /// 这个自定义特性只是用来标记类,Nunit发现这个标记的类会调用我们插件的逻辑来构建测试类 /// </summary> [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] public sealed class SampleFixtureExtensionAttribute : Attribute { } }
下面是一个插件的主要逻辑:
using System; using NUnit.Core.Builders; using NUnit.Core.Extensibility; namespace NUnit.Core.Extensions { // 此处应用NUnitAddin特性 [NUnitAddin(Description="这个插件的就是包装了NUnitTestFixture,对SetUp 和 TearDown 的逻辑进行改变。 ")] public class SampleFixtureExtensionBuilder : ISuiteBuilder, IAddin { #region ISuiteBuilder Members public bool CanBuildFrom(Type type) { return Reflect.HasAttribute( type, "NUnit.Core.Extensions.SampleFixtureExtensionAttribute", false ); } public Test BuildFrom(Type type) { if (CanBuildFrom(type)) { return new SampleFixtureExtension(type); } return null; } #endregion #region IAddin Members public bool Install(IExtensionHost host) { IExtensionPoint suiteBuilders = host.GetExtensionPoint( "SuiteBuilders" ); if ( suiteBuilders == null ) return false; suiteBuilders.Install( this ); return true; } #endregion } }
我们可以看到这个插件实现了ISuiteBuilder接口,是对SuiteBuilder扩展点进行的扩展,扩展点的主要作用就是从类构建测试类的。当Nunit启动时,会扫描addins目录,加载所有程序集,扫描其中的所有公共类,如果实现了NUnitAddin,就会把该类型存储进一个数组中。而当core初始化时,会先加载扩展点和内建的扩展,然后遍历之前的数组,构造这个类的实例,对我们的例子就是SampleFixtureExtensionBuilder 类,转型为IAddIn调用Install并把当前host做为参数传入。如上文所述,被扩展的host实现了IExtensionHost接口,我们的install方法内部就用它的GetExtensionPoint方法获取扩展点对象,此例中获取SuiteBuilders,获取扩展点,它们实现了IExtensionPoint或IExtensionPoint2接口,调用扩展点对象的Install方法,把扩展本身传入。所有扩展点的Install方法都是继承自ExtensionPoint的,因此所有的扩展点的install方法逻辑都是一样的,只不过此处采用的模板模式,会调用各个扩展点类的检查,是不是合适的扩展,然后加入了扩展集合。
这些还是不够的,我们想把以自定义特性SampleFixtureExtensionAttribute 的类构建成什么样的测试类呢?
using System; namespace NUnit.Core.Extensions { /// <summary> /// 此类继承自NUnitTestFixture,对可以扩展的方法进行重写 /// </summary> class SampleFixtureExtension : NUnitTestFixture { public SampleFixtureExtension( Type fixtureType ) : base( fixtureType ) { // 这里不需要做什么,因为我们使用了基类的构造 } // 下面是我们重写了基类的方法 protected override void DoOneTimeSetUp(TestResult suiteResult) { Console.WriteLine( "Extended Fixture SetUp called" ); base.DoOneTimeSetUp (suiteResult); } protected override void DoOneTimeTearDown(TestResult suiteResult) { base.DoOneTimeTearDown (suiteResult); Console.WriteLine( "Extended Fixture TearDown called" ); } } }
好,扩展大功告成,再来看我们扩展里的ISuiteBuilder 成员中的BuildFrom方法,它会把符合条件的类构建成SampleFixtureExtension 类型的类,至于是怎么构建的,这个我们调用了基类NUnitTestFixture的构造方法。
下面建一个测试项目引用我们的插件项目生成的dll,新建一个测试类,来测试下我们的插件。
using System; using NUnit.Framework; using NUnit.Core.Extensions; namespace NUnit.Extensions.Tests { /// <summary> /// Test class that demonstrates SampleFixtureExtension /// </summary> [SampleFixtureExtension] public class SampleFixtureExtensionTests { [TestFixtureSetUp] public void SetUpTests() { Console.WriteLine( "TestFixtureSetUp called" ); } [TestFixtureTearDown] public void FixtureTearDown() { Console.WriteLine( "TestFixtureTearDown called" ); } [Test] public void SomeTest() { Assert.IsEmpty(""); } [Test] public void AnotherTest() { Assert.IsNaN(5d); } } }
编译测试项目。
把编译好的插件的dll复制一份到nunit安装目录的addins目录下,运行nunit.exe,这时点击Tools菜单的addin,我们可以看到插件列表,这里可以看到我们的插件。然后添加你的测试项目进来,run。在输出的Text output选项卡中,可以看到:
Extended Fixture SetUp called
TestFixtureSetUp called
TestFixtureTearDown called
Extended Fixture TearDown called
可知,我们插件已经正常工作了,我们新加了nunit本身所没有的行为,插件的目的也就达到了。
但是,为什么我们测试类另两个普通的测试方法没有运行呢?你可以看一下nunit界面测试树上,根本就没有这两个方法的结点?我们知道我们构建的测试类继承自NUnitTestFixture的,这个类的构造函数只会处理其中标记为setup和teardown方法,因此其它两个普通的方法根本没被构建,因此测试的树上就没有这两个方法的结点,更不要说运行了。
如果
如果我们把测试类上的SampleFixtureExtension标记移除掉,结果如何呢?
移除之后重新编译,再来看界面的测试树,SomeTest和AnotherTest的结点已经出现了:由此我们可以知道nunit监视测试项目的dll,当它改变时,就会构建测试。此时这个测试类只是一个变通的测试类,这两个方法也被构建了。Run,结果??
有一个错误,在Errors or failures选项卡上,我们得到这样的信息:
NUnit.Extensions.Tests.SampleFixtureExtensionTests.AnotherTest:
Expected: NaN
But was: 5.0d
这个断言失败了,正是我们想的结果。
在Text output选项卡,有这样的信息:
TestFixtureSetUp called
***** NUnit.Extensions.Tests.SampleFixtureExtensionTests.SomeTest
TestFixtureTearDown called
这就是最简单的测试行为了。
《完》