今天的主題是要針對開發 .NET 應用程式最常遇到的資料庫處理(參考文章:Easily Perform LINQ Mocking to Unit Test ASP.NET Core Application),而資料庫處理手法有數種方式如原生 SQL、Entity Framework、Dapper 等手法;其中,我們以 Entity Framework 為本次的範例,再討論 Entity Framework 之前,要先了解 C# 很常用的資料處理語法 —— LINQ。
LINQ 是一套可做為 .NET 開發環境中 C# 資料物件的查詢、傳遞、轉換的語法框架,也就是在 .NET 環境中可以用 SQL 的邏輯對資料進行處理;舉例來說,若沒有 LINQ 語法之前,假設現在有一組 C# 物件要取出符合條件的資料,程式碼如下:
class Program
{
static void Main()
{
List<string> classmates = new List<string> { "David", "Mary", "Mandy", "Kevin" };
List<string> classmatesMStart = new List<string>();
foreach (string classmate in classmates)
{
if (classmate[0].Equals("M"))
{
classmatesMStart.Add(classmate);
}
}
foreach (string classmate in classmates)
{
Console.WriteLine(classmate);
}
}
}
但如果今天是用 LINQ 語法,則程式碼如下:
class Program
{
static void Main()
{
List<string> classmates = new List<string> { "David", "Mary", "Mandy", "Kevin" };
List<string> classmateMStart = classmates.Where(c => c[0].Equals("M")).ToList();
foreach (string classmate in classmates)
{
Console.WriteLine(classmate);
}
}
}
所以實務上還滿常使用 LINQ,那接下來概述 Entity Framework 的功能。
Entity Framework 是應用於 .NET 與 SQL 之間溝通的橋樑,採用 (ORM, Object Relational Mapping) 技術,使用 Entity Framework 的好處是像資料庫連線、關閉等等都已經處理好,只需專注在商業邏輯的撰寫上即可。其中,使用 Entity Framework 會自動產製類似如下的程式碼,這部分主要是跟 SQL 資料表之間的聯繫;同時,也是我們單元測試主要的測試目標,若想了解更細的內容,可參考 Entity Framework Tutorial。
public partial class ClassmateContext : DbContext
{
public ClassmateContext()
{
}
public ClassmateContext(DbContextOptions<ClassmateContext> options)
: base(options)
{
}
public virtual DbSet<Classmate> Classmates { get; set; }
// ...
}
圖一、Entity Framework 框架圖(圖源:Entity Framework Tutorial)
因此,今天的主題就在於如何在使用 Entity Framework 的情境下,做測試時把 Entity Framework 相關的元件抽離並注入假物件,並且假物件需符合 LINQ 可使用的格式,那我們先來看商業邏輯的部分:
public class ClassmateClass {
private ClassmateContext ct;
public ClassmateClass(ClassmateContext inCt) {
ct = inCt;
}
public int GetClassmateId(string classmateName)
{
int classmateId = 0;
if (!string.IsNullOrEmpty(classmateName))
{
classmateId = ct.Classmates
.Where(d => d.Name.Equals(classmateName))
.Select(d => d.Id)
.FirstOrDefault();
}
return classmateId;
}
}
在從商業邏輯中,我們可以看到 Id 主要是從 Context 中的 Classmate Dbset 取得相對應的資料;因此,接下來要做的事情就是把 ClassmateContext 抽換成虛設常式,程式碼如下:
public class StubClassmateContext<T>
{
public ClassmateContext GetClassmateContext()
{
var options = new DbContextOptionsBuilder<ClassmateContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var context = new ClassmateContext(options);
context.Classmates.Add(new Classmate { Id = 100, Name = "Kevin" });
context.SaveChanges();
return context;
}
}
因此,就可以透過建構函式把虛設常式注入,如下:
[Test]
public void DemoStubContextTest()
{
// Arrange
var stubCt = new StubClassmateContext<ClassmateContext>().GetClassmateContext();
var classmateClass = new ClassmateClass(stubCt);
// Act
int classmateId = classmateClass.GetClassmateId("Kevin");
// Assert
Assert.AreEqual(100, classmateId);
}
這樣就算是把 Entity Framework 連線至真實的資料庫抽換成自己設定的假資料,雖然在建立 StubClassmateContext 時所建立的連線依舊有用 Entity Framework 的語法(換言之,已經並非達到百分百自己控制的測試碼);但以能驗證從 Entity Framework 取出來的資料所做的邏輯是否正確,還是有其效益。