zoukankan      html  css  js  c++  java
  • 单元测试框架NUnit 之 Extensibility可扩展性

    你可以通过以下几种方法扩展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,数值越小,级别越高。以下值的定义,用到的时候可以调用:

    • DecoratorPriority.Default = 0
    • DecoratorPriority.First = 1
    • DecoratorPriority.Normal = 5
    • DecoratorPriority.Last = 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类型的对象或参数的数组或自定义的包含以下属性的对象:

    • Arguments
    • RunState
    • NotRunReason
    • ExpectedExceptionType
    • ExpectedExceptionName
    • ExpectedExceptionMessage
    • Result
    • Description
    • TestName

    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官方的扩展的例子。

  • 相关阅读:
    [CentOS7] 安装sogou输入法
    [CentOS7] vncviewer与windows之间的复制粘贴
    linux solr7.2+tomcat8 详细部署整合
    linux solr 安装
    linux dubbo-admin-2.6.0 环境搭建
    linux tomcat安装
    linux jdk安装
    linux Nginx-1.10.2 安装部署教程
    linux技巧---创建应用快捷方式
    linux MySQL 5.7+keepalived 主备服务器自主切换
  • 原文地址:https://www.cnblogs.com/forcertain/p/2239535.html
Copyright © 2011-2022 走看看