軟件的單元測試關注是的軟件最小可執行單元是否能夠正常執行,但是軟件是由一個個最小執行單元組成的集合體,單元與單元之間存在着種種依賴或聯系,所以在軟件開發時僅僅確保最小單元的正確往往是不夠的,為了保證軟件能夠正確運行,單元與單元之間的集成測試是非常必要。
另外上一篇文章只是介紹了如何使用xUnit.net對.Net Core程序進行簡單(無參)的單元測試以及計算代碼的覆蓋率,但是在實際的測試工作中,往往會通過語句覆蓋、條件/分支覆蓋(白盒)方式以及等價類、邊界值等(黑盒)方式來設計測試用例,這些用例的測試的關鍵點在於傳入測試方法的參數是不同的,如果使用無參測試方法那么針對不同的測試用例就需要編寫大量的測試方法,這是不現實的。
本文將從以下幾個方面,在單元測試的基礎上介紹如何完成數據驅動測試以及代碼依賴的集成測試:
集成測試簡介
集成測試簡單的理解就是在單元測試的基礎上,將各個單元根據其依賴關系集成起來,檢查代碼是否能夠正確運行,集成測試根據軟件開發方式的不同或者軟件架構不同也有不同的集成方式,比如面向過程的設計方式和面向對象設計方式、單體架構以及微服務分布式架構等等。
本文涉及到的僅僅是面向對象編程中類與類之間依賴的集成測試。
使用xUnit.Net實現簡單的數據庫集成測試
數據庫是大部分軟件系統不可缺少的組件,所以在集成測試時與數據庫集成是一種常見的測試場景,下面就開始介紹如何通過xUnit.Net來測試。
1. 編寫被測試代碼:
在被測試項目中引入EFCore以及相關組件:

添加DBContext以及相應的數據操作代碼:

注:此處使用LocalDB作為數據庫完成測試,如果使用SQL Server修改連接字符串即可。

用戶倉儲中實現了用戶添加以及根據姓名查詢的方法。
注:完成代碼編寫后,需要將實體代碼遷移到數據庫,先通過Add-Migration添加遷移代碼,然后通過update-database命令將數據更新到數據庫,遷移方法可參考:https://docs.microsoft.com/en-us/ef/core/get-started/full-dotnet/new-db
2.編寫測試代碼:

測試代碼也非常簡單,就是創建一個SQLServer的用戶倉儲實例,然后通過構造的方式將其注入到UserManager類型中使用,后續UserManager的創建、查詢用戶的方法將通過該倉儲實現。
3.測試運行結果:

可回歸的數據庫集成測試
這里的回歸指的是回歸測試,回歸測試是指當修改了舊的代碼后需要重新對舊代碼進行測試確認此次修改沒有影響到已有代碼。一般來說使用xUnit.Net實現的測試均是可回歸的,因為測試代碼不變,且測試代碼可以重復執行,另外測試代碼也被代碼版本管理工具管理,當修改代碼后重新執行所有測試方法即可。
但是與數據庫相關的測試又有所區別,數據庫是數據持久化工具,對數據的每一次操作都會被持久化,假設有兩個測試用例,第一是添加一條數據,第二是查詢這條數據的數量是否為1,那么這兩個測試在第一次運行的時候是沒有問題的,但是再次運行時第一個用例可能主鍵沖突無法被添加,或者添加成功后第二個用例查詢結果為2,導致測試失敗,這樣的測試視為不可回歸。
下面就介紹如何使用xUnit.Net實現數據庫集成的回歸測試:
使用構造方法初始化數據庫
要實現數據庫集成測試的可回歸性,其實最主要的問題在於每次執行完單元測試后數據庫的狀態都是不同的,那么解決這個問題在每次執行測試前將數據庫初始化是否就可以解決了呢?
看下面代碼:

首先為測試類添加構造方法,在構造方法中初始化數據庫。

然后在測試方法中添加用戶數量測試斷言,當添加完成用戶后,應該一共存在1個用戶。
執行測試方法:

無論執行多少次測試都是通過的,這意味着每次添加用戶后用戶數量均為1,這樣就達到了測試的可回歸性。
但是真實的開發過程中,不可能只有一個測試類型,而且如果為每一個測試類型都添加初始化數據庫的代碼,那么這些單元測試將會執行的非常慢(數據庫的初始化非常耗時),那么要如何解決這個問題?
使用Fixture共享測試上下文
Fixture是xUnit.Net中的一項特性,它可以用來共享測試上下文,Fixture可以譯為固定裝置,換句話說就是把一個內容固定住,使該內容可以在被指定的范圍內使用。xUnit.Net中Fixture有兩種類型,分別是ClassFixture和CollectionFixture,它們對應的是在一個測試類型里面共享和一個測試類型集合中共享上下文,Fixture的作用有很多,下面將介紹如何使用Fixture來共享數據庫被初始化這一狀態(不需要重復初始化):
1. 創建一個Fixture類型:
Fixture實際上是一個普通的C#類型,唯一需要注意的是它使用構造方法初始化數據、使用Dispose來釋放或者清理數據,簡單的一個Fixture類型如下:

2. 為單個類型使用ClassFixture:

從代碼中可以看到通過構造方法初始化的代碼已經被注釋了,實現IClassFixture接口后,再次執行測試方法,可以看到測試仍然是可回歸的。
3. 為多個類型使用ICollectionFixture:
首先需要在DatabaseFixture的基礎上添加一個實現ICollectionFixture的類型:

兩個要點分別是需要使用CollectionDefinition特性,然后類型需要實現ICollectionFixture<T>接口。
在測試類型上通過Collection特性,指定之前定義的Collection名稱來應用該CollectionFixture:

然后多次執行測試方法,可以發現測試仍然是可回歸的:

注:在共享測試上下數據時(包括共享數據庫數據),可能會因為測試方法執行順序導致測試失敗,但是xUnit.Net中沒有提供按順序執行實現方式,默認是按照方法命名,如果需要按照一定順序執行測試,可參考官方的例子:https://github.com/xunit/samples.xunit/tree/master/TestOrderExamples
測試數據的管理與初始化
涉及到數據庫的測試,很可能在測試之前數據庫就會存在一些已有的測試數據,使用xUnit.Net來編寫測試代碼時,可以將測試數據的初始化放到Fixture類型中,但是要如何對這些數據進行管理呢?一般來說使用SQL腳本來管理數據庫的測試數據是一種不錯的方式,因為這樣測試數據與代碼解耦,維護數據時僅需要維護SQL腳本文件即可。
下面就介紹一下如何在xUnit.Net中使用SQL腳本文件管理以及初始化數據:
1.創建一個SQL腳本:

注:需要將文件的“復制到輸出目錄”設置為“總是復制”或者“有更新復制”:

2.在Fixture類型初始化數據庫時,執行SQL腳本初始化數據:
下面代碼首先去Data目錄下查找.sql的所有文件,然后依次執行這些SQL腳本。

3.修改測試代碼(因為腳本添加了2條數據,所以期望值應該是3條):

4.執行測試:
測試成功。
數據驅動測試簡介
文章最開始提到過,不管是白盒的覆蓋測試還是黑盒的邊界/等價測試,它們最直接的體現就是“參數”,傳入被測試方法的參數。而這些參數實際上就是“測試用例”,為每一個測試用例編寫一個測試方法是不合理的,所以需要一種方法通過變換參數來完成測試,並且盡可能的重用測試代碼。這就是數據驅動測試。
基於xUnit.Net的數據驅動測試
Theory&InlineData
Theory和InlineData是xUnit.Net中提供的數據驅動(參數化)測試的功能特性,下面是一個theory以及InlineData的應用示例:

運行結果:
通過InlineData就可以輕易的將測試用例的數據應用到測試方法上,並完成期望的測試。但是當添加新的測試用例時就需要對代碼文件進行修改,測試用例放置在代碼中管理也不是非常方便,有沒有一種可以獨立管理測試用例數據的方法呢?
基於excel的數據驅動測試
xUnit.Net沒有提供內置的Excel數據加載功能,但是它的拓展性很好,可以通過實現DataAttribute實現一個Theory的數據源提供器:

1. 實現DataAttribute抽象類,實現Excel的Theory數據提供器:
下面是實現Excel數據加載的核心方法:



以上方法需要注意以下幾點:
- 兩個主體:Excel文件名稱和測試的方法的MethodInfo,前者用於讀取Excel文件,后者用來獲取參數信息。
- 通過MethodInfo獲取測試方法的參數列表包括參數類型。
- 讀取Excel數據並於測試方法的參數列表匹配,簡單來說就是Excel中每一行的數據就是對應測試方法的參數,有多少行數據測試方法就會被執行多少次。
- 當Excel中數據讀取為double或者float但是測試方法需要int時,需要進行轉換。
注:此處使用的Excel文件讀取組件為:https://github.com/ExcelDataReader/ExcelDataReader
DataAttribute完整代碼見:https://github.com/yqszt/xUnitTestDemo/blob/master/XUnitTestProject/ExcelDataAttribute.cs
2.使用ExcelDataAttribute:

3. 在AdditionTestCase.xls中添加測試用例數據:

注:需要將AdditionTestCase.xls的復制到輸出目錄屬性設置為“總是復制”或“復制新文件”。
4.執行測試方法:

測試成功。
小結
本文主要介紹了.Net Core使用xUnit.Net實現了與數據的集成測試並通過Fixture的方式實現了數據庫的初始化,保證了測試的可回歸性。另外還介紹了如何使用xUnit.Net來實現數據驅動測試,數據驅動測試可以簡單地通過添加用例數據的方式來提高測試質量,還可以便捷的對測試用例進行管理。
本文示例代碼:https://github.com/yqszt/xUnitTestDemo
參考:
https://en.wikipedia.org/wiki/Integration_testing
https://en.wikipedia.org/wiki/Data-driven_testing
https://xunit.github.io/docs/getting-started-dotnet-core#write-first-theory
http://dotnetliberty.com/index.php/2015/12/31/fast-asp-net-5-integration-testing-with-xunit/
https://github.com/MisterJames/GenFu
http://ikeptwalking.com/writing-data-driven-tests-using-xunit/
https://wandering.life/data-driven-testing-xunit-vs2017/
https://github.com/ExcelDataReader/ExcelDataReader
http://xunitpatterns.com/Testing%20With%20Databases.html
https://xunit.github.io/docs/shared-context
