上一篇文章《[小北De編程手記] : Lesson 03 玩轉 xUnit.Net 之 Fixture(上)》向大家介紹了xUnit.Net 共享數據的方式、Test Case的構造函數 & IDisposable.Dispose、Class級別的Fixture : IClassFixture。這一篇,我們接着講解后面的內容,回顧一下本文要討論的內容:
- xUnit.Net 共享數據的方式(上)
- Test Case的構造函數 & IDisposable.Dispose(上)
- Class級別的Fixture : IClassFixture(上)
- Collection級別的Fixture : ICollectionFixture(下)
- 依賴注入以及輸出日志(下)
(四)Collection級別的Fixture : ICollectionFixture
回想一下上一篇中我們虛擬的應用場景。其中,關於問題三:“在應用程序級別統一創建數據庫連接,Test Case 使用的數據庫連接是同一份(或是統一管理的)”。 針對這一需求的實現,我們可以使用xUnit.Net的ICollectionFixture來實現。Collection級別的Fixture為我們提供了可以在多個測試類之間數據共享的能力。包含在同一個Collection之下的所有測試用例共享一份上下文數據。下面我們就來動手實現一下虛擬場景問題三之中的那個功能吧。
Step 01:定義CollectionFixture(Demo中的DatabaseFixture)
與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。
對於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的測試類。
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的法則,用來削減計算機程序的耦合問題。如今已成為許多不同領域軟件框架的核心。關於依賴注入的概念,我想大家都不會陌生。這里我列出了幾種依賴注入的主要方式:
- 類型1 (基於接口): 可服務的對象需要實現一個專門的接口,該接口提供了一個對象,可以重用這個對象查找依賴(其它服務)。
- 類型2 (基於setter): 通過JavaBean的屬性(setter方法)為可服務對象指定服務。
- 類型3 (基於構造函數): 通過構造函數的參數為可服務對象指定服務。
這里談到依賴注入,主要是想跟大家分享本人對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 共享數據的方式
- Test Case的構造函數 & IDisposable.Dispose
- Class級別的Fixture : IClassFixture
- Collection級別的Fixture : ICollectionFixture
- 依賴注入以及輸出日志
關於的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