在使用xUnit.Net Framework構建單元測試或自動化測試項目的時候,無論是針對一些比較耗費資源的對象亦或是為了支持Test case預設數據的能力,我們都需要有一些初始化或是清理相關的動作。在xUnit.Net中,提供了多種方式來滿足我們的需要。還是照例看一下本文要討論的內容:
- xUnit.Net 共享數據的方式(上)
- Test Case的構造函數 & IDisposable.Dispose(上)
- Class級別的Fixture : IClassFixture(上)
- Collection級別的Fixture : ICollectionFixture(下)
- 依賴注入以及輸出日志(下)
本文我們只是討論前三個議題,其他的議題我會在《xUnit.Net 之 Fixture(下)》中討論。
首先,我們虛擬一個自動化測試中很容易遇到的一個場景。有四個Test case如下圖所示:
可以看到,其中前三個Case(測試功能01,02,03)步驟如下,創建DB連接->打開瀏覽器->執行功能->關閉瀏覽器->釋放數據庫連接。而功能測試04僅僅是需要操作數據庫而已,沒有操作瀏覽器的需求。因此,不需要在瀏覽器中進行操作。對於這樣的一個場景,直接能想到的方法是編寫4個測試方法打上前面的[Fact]標簽,每個方法中創建數據庫連接,打開瀏覽器,操作,然后釋放數據庫,關閉瀏覽器。但這樣的做法有很多的問題,比如會多次占用瀏覽器驅動和數據庫連接這樣的非托管資源。而打開數據庫連接和瀏覽器驅動往往是比較耗時的操作,多次打開會無端的增加Test case的運行時間。
那么,如何來設計測試步驟呢?這里,我建議滿足下面幾個條件(當然也是為了講解今天的內容):
- 在每個測試任務開始之前做一些數據的初始化工作。
- 打開一次瀏覽器,完成測試功能01,02,03之后,在關閉瀏覽器。在減少創建瀏覽器的開銷的同時節省了測試時間
- 在應用程序級別統一創建數據庫連接,Test Case 使用的數據庫連接是同一份(或是統一管理的)。
(一)xUnit.Net 共享數據的方式
對於之前描述的業務場景,我們需要在每個Test Case執行前后,一組Test Case執行前后,所有Test Case執行前后這三個維度上添加自定義的操作。對應下來:
- 每個Test Case執行前后 : 在每個測試任務開始之前做一些數據的初始化工作。
- 一組Test Case執行前后 : 打開一次瀏覽器,完成測試功能01,02,03之后,再關閉瀏覽器。
- 所有Test Case執行前后 : 在應用程序級別統一創建數據庫連接。
如圖所示,CollectionFixture可以用於添加所有Test Case執行前后的一些操作(即例子中的創建和銷毀數據庫連接)。對於部分Case需要初始化瀏覽器,我們可以使用ClassFixture提供的功能。每個Case執行前后的操作我們可以使用測試類構造函數和IDisposable.Dispose來進行處理。下面我就逐一為大家講解如何使用這些功能。
(二)Test Case的構造函數 & IDisposable.Dispose
如何在每個Test Case執行前后做處理?這應該是每個使用過單元測試框架的同學都知道的。多數的框架都是通過打標簽的方式來提供類似功能的,例如:NUnit的[Setup]和[TearDown] , MSTest的[TestInitialize]和[TestCleanup]。而xUnit.Net提供了一種更加優雅的處理方式,就是利用構造函數以及IDisposable.Dispose方法來實現對應的功能。懂得一些面向對象的小伙伴也許會發現,這樣的改進主要是為了支持依賴注入(而不是簡單的省去了標簽而已)。這也為我后續的文章中要講到的許多功能的注入實現提供了可能。講了這么多理論,先上一段Code:
1 namespace Demo.UnitTest.Lesson03_Fixture 2 { 3 public class SharedContext_Constructor : IDisposable 4 { 5 private ITestOutputHelper _output; 6 public SharedContext_Constructor(ITestOutputHelper output) 7 { 8 this._output = output; 9 _output.WriteLine("Execute constructor!"); 10 } 11 12 #region Test case 13 [Fact(DisplayName = "SharedContext.Constructor.Case01")] 14 public void TestCase01() 15 { 16 _output.WriteLine("Execute case 01!"); 17 } 18 19 [Fact(DisplayName = "SharedContext.Constructor.Case02")] 20 public void TestCase02() 21 { 22 _output.WriteLine("Execute case 02!"); 23 } 24 25 [Fact(DisplayName = "SharedContext.Constructor.Case03")] 26 public void TestCase03() 27 { 28 _output.WriteLine("Execute case 03!"); 29 } 30 #endregion 31 32 public void Dispose() 33 { 34 _output.WriteLine("Execute dispose!"); 35 } 36 } 37 }
代碼中的ITestOutputHelper就是通過構造函數注入的方式為我們提供了輸出Log的能力(這個下一篇的文章我會為大家講解),這里你只需要知道他是可以輸出一些日志的即可。上面的Code中,有3個Case,Case執行的時Runner會在執行每個Case前后分別調用測試類的構造函數和對應的Dispose方法。輸出如下,我們可以看到測試類構造函數和Dispose方法在每一個Case執行前后都被執行。
(三)Class級別的Fixture : IClassFixture
ok,現在我們考慮前文中提到的問題二:僅僅打開一次瀏覽器,完成測試功能01,02,03之后,再關閉瀏覽器。xUnit.Net為我們提供了基於類級別的Fixture,即IClassFixture。IClassFixture是一個泛型接口(標記接口,沒有任何需要實現的方法),接受一個類型。該類型的構造函數會在測試類中的第一個Test Case運行之前被調用。而其IDisposable.Dispose方法會在測試類中最后一個測試方法執行完成之后被執行。IClassFixture定義如下:
1 namespace Xunit 2 { 3 public interface IClassFixture<TFixture> where TFixture : class 4 { 5 } 6 }
如何使用IClassFixture呢?步驟如下:
Step 01 : 創建自定義的Fixture類,添加構造函數和IDisposable接口的實現方法。本文主要是講解xUnit.Net的使用,因此示例代碼中我沒有給出創建瀏覽器驅動的具體代碼(這部分內容可以參見我的另一個系列《[小北De編程手記] : Selenium For C# 教程》),只是添加了ExecuteCount屬性用於標記執行次數,代碼如下:
1 public class SingleBrowserFixture : IDisposable 2 { 3 public int UserId { get; set; } 4 public string UserName { get; set; } 5 public static int ExecuteCount; 6 7 public SingleBrowserFixture() 8 { 9 this.UserId = 1; 10 this.UserName = "North"; 11 ExecuteCount++; 12 13 //打開瀏覽器... 14 } 15 16 public void Dispose() 17 { 18 //關閉瀏覽器... 19 } 20 }
Step 02 :創建具體的測試類,並繼承 IClassFixture<SingleBrowserFixture>(注意:我們用接口標記需要使用哪一個類)
Step 03 :在測試類中獲取Fixture對象,xUnit.Net 用構造函數注入的方式提供了獲取IClassFixture標記對象的方法。我們可以在測試類的構造函數中添加對應的注入參數來獲取Fixture,這樣的設計使得我們在測試類中所有的測試用例中共享一些Context數據,xUnit.Net執行測試用例的時候會自動識別構造參數的類型是否和IClassFixture所標記的類型是否匹配。代碼如下:
1 public class SharedContext_ClassFixture : IClassFixture<SingleBrowserFixture> 2 { 3 ITestOutputHelper _output; 4 SingleBrowserFixture _fixture; 5 static int _count; 6 public SharedContext_ClassFixture(ITestOutputHelper output, SingleBrowserFixture fixture) 7 { 8 _output = output; 9 _fixture = fixture; 10 _count++; 11 } 12 #region Test case 13 [Fact(DisplayName = "SharedContext.ClassFixture.Case01")] 14 public void TestCase01() 15 { 16 _output.WriteLine("Execute case 01! Current User:[{0}]-{1}", _fixture.UserId, _fixture.UserName); 17 _output.WriteLine("Execute count! Constructor:[{0}] , ClassFixture:[{1}]", _count, SingleBrowserFixture.ExecuteCount); 18 19 } 20 21 [Fact(DisplayName = "SharedContext.ClassFixture.Case02")] 22 public void TestCase02() 23 { 24 _output.WriteLine("Execute case 01! Current User:[{0}]-{1}", _fixture.UserId, _fixture.UserName); 25 _output.WriteLine("Execute count! Constructor:[{0}] , ClassFixture:[{1}]", _count, SingleBrowserFixture.ExecuteCount); 26 } 27 #endregion Test case 28 }
代碼中可以看到,我用_count 標記了測試類的執行次數,用ExecuteCount標記Fixture類的執行次數,看下運行結果:
可以看到,測試類的構造被執行了2次(也就是每個測試用例執行的時候都會執行一次),而ClassFixture標記的測試類中的構造函數只是被執行了一次。IDisposable.Dispose 也具有相同的邏輯。
下一篇,為大家介紹:
- Collection級別的Fixture : ICollectionFixture(下)
- 依賴注入以及輸出日志(下)
小北De系列文章:
《[小北De編程手記] : Selenium For C# 教程》
《[小北De編程手記]:C# 進化史》(未完成)
《[小北De編程手記]:玩轉 xUnit.Net》(未完成)
Demo地址:https://github.com/DemoCnblogs/xUnit.Net