首先定义一个自定义的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
这就是最简单的测试行为了。
《完》