上一篇文章《[小北De编程手记] : Lesson 03 玩转 xUnit.Net 之 Fixture(上)》向你们介绍了xUnit.Net 共享数据的方式、Test Case的构造函数 & IDisposable.Dispose、Class级别的Fixture : IClassFixture。这一篇,咱们接着讲解后面的内容,回顾一下本文要讨论的内容:html
回想一下上一篇中咱们虚拟的应用场景。其中,关于问题三:“在应用程序级别统一建立数据库链接,Test Case 使用的数据库链接是同一份(或是统一管理的)”。 针对这一需求的实现,咱们可使用xUnit.Net的ICollectionFixture来实现。Collection级别的Fixture为咱们提供了能够在多个测试类之间数据共享的能力。包含在同一个Collection之下的全部测试用例共享一份上下文数据。下面咱们就来动手实现一下虚拟场景问题三之中的那个功能吧。git
Step 01:定义CollectionFixture(Demo中的DatabaseFixture)github
与ClassFixture相似,自定义的CollectionFixture类,须要完成其构造函数 & IDisposable.Dispose的定义。而CollectionFixture类的构造和Dispose方法最终会在全部被标记使用该Collection的Test Class对应的Case执行先后被调用。即全部标记使用该Collection的测试方法运行以前会执行CollectionFixture的构造函数。全部标记使用该Collection的测试方法所有运行完毕以后会执行CollectionFixture的IDisposable.Dispose函数。咱们定义一个DatabaseFixture,代码以下:数据库
1 public class DatabaseFixture : IDisposable 2 { 3 public object DatabaseContext { get; set; } 4 5 public static int ExecuteCount { get; set; } 6 7 public DatabaseFixture() 8 { 9 ExecuteCount++; 10 //初始化数据链接 11 } 12 13 public void Dispose() 14 { 15 //销毁数据链接 16 } 17 }
代码中,省略了得建立和销毁数据库链接的Code。只是使用了一个object类型的属性来表示数据库上下文,而且建立了一个静态变量ExecuteCount用于标记构造函数的使用频率。编程
Step 02:定义Collection。app
对于ClassFixture而言,由于是基于Class级别的数据共享。so... ... xUnit.Net提供了直接用类继承IClassFixture接口并结合构造函数注入的方式优雅的实现了数据共享的功能。而对于Collection(一组类)的数据共享又该如何实现呢?先看一下示例代码:框架
1 /// <summary> 2 /// 定义Collection名称,标明使用的Fixture 3 /// </summary> 4 [CollectionDefinition("DatabaseCollection")] 5 public class DatabaseCollection : ICollectionFixture<DatabaseFixture> 6 { 7 }
能够看到,咱们定义了一个没有任何内容的类DatabaseCollection,该类的主要功能是定义了一个名字为“DatabaseCollection”(此名称能够和类名不一样)的Collection,并指明该Collection所对应了Fixture。须要说明的是ICollectionFixture和IClassFixture同样是一个泛型标记接口(即没有任何须要实现的方法,只是用来标记对应的Fixture的类型)。而定义Collection代码中使用了CollectionDefinition标签,其定义以下:函数
1 namespace Xunit 2 { 3 // Summary: 4 // Used to declare a test collection container class. The container class gives 5 // developers a place to attach interfaces like Xunit.IClassFixture<TFixture> 6 // and Xunit.ICollectionFixture<TFixture> that will be applied to all tests 7 // classes that are members of the test collection. 8 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] 9 public sealed class CollectionDefinitionAttribute : Attribute 10 { 11 // Summary: 12 // Initializes a new instance of the Xunit.CollectionDefinitionAttribute class. 13 // 14 // Parameters: 15 // name: 16 // The test collection name. 17 public CollectionDefinitionAttribute(string name); 18 } 19 }
被CollectionDefinition标记的Class在运行时会被xUnit.Net框架实例化为一个对象,该对象将用于标记其余的Class(有兴趣的话能够去GitHub看看xUnit.Net的源代码)。这里须要一个CollectionName做为参数,该参数将会用标记那些须要使用这个CollectionFixture的类。工具
Step 03:用Collection来标记须要使用Fixtrue的测试类。post
xUnit.Net提供了Collection类,它的做用是用来指明测试类须要使用哪一个Collection的。全部被标记了Collection测试类中的测试方法在其运行以前会调用一次对应的CollectionFixture的构造函数,全部方法运行完毕以后会调用一次CollectionFixture的IDisposable.Dispose函数(若是定义了的话)。值得注意的是测试类中依旧是经过构造函数注入的方式获取DatabaseFixture实例对象的。那么,咱们来看一下Demo:
1 [Collection("DatabaseCollection")] 2 public class SharedContext_CollectionFixture_01 3 { 4 private DatabaseFixture _dbFixture; 5 private ITestOutputHelper _output; 6 public SharedContext_CollectionFixture_01(ITestOutputHelper output, DatabaseFixture dbFixture) 7 { 8 _dbFixture = dbFixture; 9 _output = output; 10 } 11 12 [Fact(DisplayName = "SharedContext.CollectionFixture.Case01")] 13 public void TestCase01() 14 { 15 _output.WriteLine("Execute CollectionFixture case 01!"); 16 _output.WriteLine("DatabaseFixture ExecuteCount is : {0}", DatabaseFixture.ExecuteCount); 17 } 18 } 19 20 [Collection("DatabaseCollection")] 21 public class SharedContext_CollectionFixture_02 22 { 23 private DatabaseFixture _dbFixture; 24 private ITestOutputHelper _output; 25 public SharedContext_CollectionFixture_02(DatabaseFixture dbFixture, ITestOutputHelper output) 26 { 27 _dbFixture = dbFixture; 28 _output = output; 29 } 30 31 [Fact(DisplayName = "SharedContext.CollectionFixture.Case02")] 32 public void TestCase01() 33 { 34 _output.WriteLine("Execute CollectionFixture case 02!"); 35 _output.WriteLine("DatabaseFixture ExecuteCount is : {0}", DatabaseFixture.ExecuteCount); 36 } 37 }
Dome中定义了两个测试类,每一个测试类中有一个测试方法,并用Collection指明了须要使用的Collection的名称。运行结果以下:
能够看到,DatabaseFixture中的构造函数只是被执行了一次(IDisposable.Dispose也有相同的逻辑)。所以,实际的单元测试中,咱们能够此处构建、管理数据库链接以节省资源的开销。
依赖注入是一个重要的OOP的法则,用来削减计算机程序的耦合问题。现在已成为许多不一样领域软件框架的核心。关于依赖注入的概念,我想你们都不会陌生。这里我列出了几种依赖注入的主要方式:
这里谈到依赖注入,主要是想跟你们分享本人对xUnit.Net的设计理念的一点点理解。我在第一篇xUnit.Net系列文章《[小北De编程手记] : Lesson 01 玩转 xUnit.Net 之 概述》中曾提到过:xUnit.Net的一个改进就是在处理每一个Test Case的初始化和清理方法时再也不使用属性标签来标记,而是采用了构造函数和IDisposable.Dispose方法。这样作的一个直接好处就是使得依赖注入更容易的运用于xUnit.Net之中。前面例子中各个级别的Fixture,日志对象... ...都是经过依赖注入的方式简单,优雅的被咱们所获取到。而对于日志对象,使用者也无需去关注它会输出到哪里(这个是由运行Case的工具<即Runner>决定),咱们甚至不用关心它是如何被实例化。当使用不一样的Runner运行Case时,Runner会针对xUnit.net的接口去实现一套属于本身的输出方式。下面咱们来回顾一下输出接口以及它的使用方式:
1 namespace Xunit.Abstractions 2 { 3 public interface ITestOutputHelper 4 { 5 void WriteLine(string message); 6 void WriteLine(string format, params object[] args); 7 } 8 }
能够看到,ITestOutputHelper定义了两个输出方法,使用者能够经过下面的方式(构造函数注入)获取到运行时Runner提供的输出对象。而关于对象的实例化,管理等操做都是由运行Case的Runner(程序)来管理的。后面我会为你们讲解如何自定义Runner以及自定义Runner的意义所在,这里就再也不赘述了。
1 public class SharedContext_ClassFixture : IClassFixture<SingleBrowserFixture> 2 { 3 ITestOutputHelper _output; 4 public SharedContext_ClassFixture(ITestOutputHelper output , SingleBrowserFixture fixture) 5 { 6 _output = output; 7 } 8 #region Test case 9 [Fact(DisplayName = "SharedContext.ClassFixture.Case01")] 10 public void TestCase01() 11 { 12 _output.WriteLine("Log here"); 13 } 14 #endregion Test case 15 }
日志对象自己的使用很简单,单独拉出来说是为了向你们展现xUnit.Net设计的工匠精神(更靠近设计者的意图)。不少框架级别的改变虽小(NUnit使用属性标签标记初始化方法,而xUnit.Net使用构造函数),可是用意颇深。so... ... 咱们就慢慢体会吧~~~
这两篇文章主要和你们探讨了如下问题:
关于的xUnit.Net Fixture的基本使用就先介绍到这里了,下一篇为你们讲解一下如何在Fixture的层面上扩展xUnit.Net的功能。到时候,让咱们一块儿来看看xUnit.Net在可扩展性方面有何过人之处?
小北De系列文章:
《[小北De编程手记] : Selenium For C# 教程》
《[小北De编程手记]:C# 进化史》(未完成)
《[小北De编程手记]:玩转 xUnit.Net》(未完成)
Demo地址:https://github.com/DemoCnblogs/xUnit.Net