選擇測試策略

如概 中所述,您需要做出的基本決策是您的測試是否牽涉到生產資料庫系統,就像您的應用程式一樣,或您的測試是否會針對 測試雙精度浮 點執行,這會取代您的生產資料庫系統。

針對實際的外部資源進行測試 -- 而不是將它取代為測試雙精度浮點數 - 可能會涉及下列困難:

  1. 在許多情況下,對實際的外部資源進行測試是不可能或實用的。 例如,您的應用程式可能會與某些無法輕易測試的服務互動(因為速率限制,或缺少測試環境)。
  2. 即使可能牽涉到真正的外部資源,也可能非常緩慢:對雲端服務執行大量測試可能會導致測試花費太長的時間。 測試應該是開發人員日常工作流程的一部分,因此測試必須快速執行。
  3. 針對外部資源執行測試可能會涉及隔離問題,其中測試會互相干擾。 例如,針對資料庫平行執行的多個測試可能會修改資料,並導致彼此以各種方式失敗。 使用測試雙精度浮點數可避免這種情況,因為每個測試都會針對自己的記憶體內部資源執行,因此會自然地與其他測試隔離。

不過,針對測試雙精度浮點數通過的測試並不保證程式在針對實際外部資源執行時運作。 例如,資料庫測試雙精度浮點數可能會執行區分大小寫的字串比較,而生產資料庫系統則執行不區分大小寫的比較。 只有在針對實際生產資料庫執行測試時,才會發現這類問題,使得這些測試成為任何測試策略的重要部分。

針對資料庫進行測試可能比看起來更容易

由於上述針對實際資料庫進行測試時發生困難,開發人員經常會先敦促使用測試雙精度浮點數,並具有強固的測試套件,可在其機器上經常執行:相反地,涉及資料庫的測試應該執行的頻率要低得多,而且在許多情況下也提供較少的涵蓋範圍。 我們建議對後者進行更多思考,並建議資料庫實際上受到上述問題的影響可能遠不如人們所想:

  1. 大部分的資料庫現在可以輕鬆地安裝在開發人員的電腦上。 Docker 等容器型技術可讓您輕鬆完成,Github 工作區 開發容器 等 技術會為您設定整個開發環境(包括資料庫)。 使用 SQL Server 時,也可以針對 Windows 上的 LocalDB 進行測試,或輕鬆地在 Linux 上設定 Docker 映射。
  2. 針對具有合理測試資料集的本機資料庫進行測試通常非常快速:通訊完全是本機的,而且測試資料通常會在資料庫端的記憶體中緩衝處理。 EF Core 本身僅包含對 SQL Server 的 30,000 項測試;這些作業會在幾分鐘內可靠地執行,並在每個單一認可上以 CI 執行,而且經常由開發人員在本機執行。 一些開發人員轉向記憶體內部資料庫(一個「假」),相信這是需要速度 -- 這幾乎從來不是實際的情況。
  3. 針對真實資料庫執行測試時,隔離確實是障礙,因為測試可能會修改資料並互相干擾。 不過,有各種技術可在資料庫測試案例中提供隔離;我們專注于針對生產資料庫系統 進行測試中的這些專案

上述不是為了貶低測試雙打或反對使用雙打。 有一件事,某些無法測試的案例需要測試雙精度浮點數,例如模擬資料庫失敗。 不過,在我們的經驗中,使用者經常因為上述原因而回避對其資料庫進行測試,認為其速度緩慢、硬或不可靠,但情況不一定如此。 針對生產資料庫系統 進行測試旨在解決此問題,提供針對資料庫快速、隔離測試的指導方針和範例。

不同類型的測試雙精度浮點數

測試雙精度浮 點數是一個廣泛的詞彙,其中包含非常不同的方法。 本節涵蓋一些常見的技術,涉及測試 EF Core 應用程式的測試雙精度浮點數:

  1. 使用 SQLite (記憶體內部模式) 作為資料庫假的,取代您的生產資料庫系統。
  2. 使用 EF Core 記憶體內部提供者做為假資料庫,取代您的生產資料庫系統。
  3. 模擬或存根 DbContextDbSet
  4. 介紹 EF Core 與應用程式程式碼之間的存放庫層,以及模擬或存根該層。

接下來,我們將探索每個方法的意義,並將其與其他方法進行比較。 建議您閱讀不同的方法,以充分瞭解每個方法。 如果您決定撰寫不涉及生產資料庫系統的測試,則存放庫層是唯一允許對資料層進行全面且可靠模擬的方法。 不過,這種方法在實作和維護方面具有顯著的成本。

SQLite 作為資料庫假

其中一個可能的測試方法是將生產資料庫(例如 SQL Server)與 SQLite 交換,有效地使用它作為測試「假」。 除了易於設定之外,SQLite 還有一項 記憶體內部資料庫 功能,特別適合用於測試:每個測試自然都會在自己的記憶體內部資料庫中隔離,而且不需要管理實際檔案。

不過,在執行此動作之前,請務必瞭解在 EF Core 中,不同的資料庫提供者的行為不同 - EF Core 不會嘗試抽象基礎資料庫系統的每個層面。 基本上,這表示針對 SQLite 進行測試並不保證與 SQL Server 或任何其他資料庫的結果相同。 以下是一些可能的行為差異範例:

  • 相同的 LINQ 查詢可能會在不同的提供者上傳回不同的結果。 例如,SQL Server 預設會執行不區分大小寫的字串比較,而 SQLite 則區分大小寫。 這可讓測試針對 SQLite 進行測試,而 SQL Server 會失敗(反之亦然)。
  • SQLite 僅支援在 SQL Server 上運作的某些查詢,因為這兩個資料庫中的確切 SQL 支援不同。
  • 如果您的查詢碰巧使用提供者特定的方法,例如 SQL Server 的 EF.Functions.DateDiffDay ,該查詢將會在 SQLite 上失敗,而且無法進行測試。
  • 原始 SQL 可能會運作,或可能會失敗或傳回不同的結果,視所執行的確切情況而定。 SQL 方言在資料庫中有許多方式不同。

相較于針對生產資料庫系統執行測試,開始使用 SQLite 相當容易,而且有許多使用者這樣做。 不幸的是,測試 EF Core 應用程式時,上述限制最終會變成問題,即使它們似乎不在一開始也一樣。 因此,建議您針對實際資料庫撰寫測試,或如果使用測試雙精度浮點數是絕對必要專案,請依照以下所述將存放庫模式的成本上線。

如需如何使用 SQLite 進行測試的資訊, 請參閱本節

作為資料庫假的記憶體內部

作為 SQLite 的替代方案,EF Core 也隨附記憶體內部提供者。 雖然此提供者最初設計為支援 EF Core 本身的內部測試,但有些開發人員在測試 EF Core 應用程式時會將其當做資料庫假用。 強烈建議您這麼做 :因為資料庫是假的,記憶體內部與 SQLite 有相同的問題(請參閱上述),但此外還有下列額外限制:

  • 記憶體內部提供者通常支援比 SQLite 提供者更少的查詢類型,因為它不是關係資料庫。 相較于生產資料庫,更多查詢將會失敗或行為不同。
  • 不支援交易。
  • 原始 SQL 完全不受支援。 將此與 SQLite 進行比較,只要 SQL 在 SQLite 和生產資料庫上以相同方式運作,就可以使用原始 SQL。
  • 記憶體內部提供者尚未針對效能進行優化,而且通常會比記憶體內部模式中的 SQLite 更慢(甚至您的生產資料庫系統)。

總而言之,記憶體內部具有 SQLite 的所有缺點,以及一些缺點,而且沒有回報的優點。 如果您要尋找簡單的記憶體內部資料庫假,請使用 SQLite,而不是記憶體內部提供者;但請考慮改用存放庫模式,如下所示。

如需如何使用記憶體內部測試的資訊,請參閱 本節

模擬或擷取 DbCoNtext 和 DbSet

此方法通常會使用模擬架構來建立 和 DbSet 的測試 DbContext 雙精度浮點數,以及針對這些雙精度浮點數的測試。 模擬 DbContext 是測試各種 非查詢 功能的好方法,例如呼叫 AddSaveChanges() ,可讓您確認程式碼在撰寫案例中呼叫它們。

不過,無法正確模擬 DbSet 查詢 功能,因為查詢是透過 LINQ 運算子來表示,這是透過 的 IQueryable 靜態擴充方法呼叫。 因此,當有些人談論「模擬 DbSet 」時,他們真正代表的是,他們會建立 DbSet 記憶體內部集合所支援的 ,然後針對記憶體中的該集合評估查詢運算子,就像一個簡單的 IEnumerable 。 這實際上不是模擬,而是一種假的,記憶體內部集合會取代實際資料庫。

由於只有 DbSet 本身是假的,而且查詢是在記憶體內部評估,因此此方法最終會與使用 EF Core 記憶體內部提供者非常類似:這兩種技術都會在 .NET 中透過記憶體內部集合執行查詢運算子。 因此,這項技術也有相同的缺點:查詢的行為會不同(例如區分大小寫),或只會失敗(例如,因為提供者特定的方法),原始 SQL 將無法運作,而且交易會忽略。 因此,通常應該避免這項技術來測試任何查詢程式碼。

存放庫模式

上述方法嘗試將 EF Core 的生產資料庫提供者與假測試提供者交換,或建立 DbSet 記憶體內部集合所支援的 。 這些技術很類似,它們仍然會評估程式的 LINQ 查詢,無論是在 SQLite 或記憶體中,這最終都是上述困難的來源:針對特定生產資料庫執行的查詢無法在沒有問題的情況下可靠地在其他地方執行。

針對適當的可靠測試雙精度浮點數,請考慮引入存放 庫層 ,以在應用程式程式碼與 EF Core 之間調解。 存放庫的生產實作包含實際的 LINQ 查詢,並透過 EF Core 加以執行。 在測試中,存放庫抽象概念會直接進行擷取或模擬,而不需要任何實際的 LINQ 查詢,有效地從測試堆疊中移除 EF Core,並允許測試單獨專注于應用程式程式碼。

下圖將資料庫假方法 (SQLite/記憶體內部) 與存放庫模式進行比較:

Comparison of fake provider with repository pattern

由於 LINQ 查詢已不再是測試的一部分,因此您可以直接將查詢結果提供給您的應用程式。 另一種方式是,上述方法大致允許擷 取查詢輸入 (例如,以記憶體內部資料表取代 SQL Server 資料表 ),但仍會執行記憶體中實際的查詢運算子。 相反地,存放庫模式可讓您直接截斷 查詢輸出 ,以便進行更強大且專注的單元測試。 請注意,若要讓此功能運作,您的存放庫無法公開任何 IQueryable 傳回方法,因為這些方法無法再次被擷取;應該改為傳回 IEnumerable。

不過,由於存放庫模式需要在 IEnumerable-returning 方法中封裝每一個(可測試的)LINQ 查詢,所以它會在您的應用程式上施加額外的架構層,而且可能會產生顯著的實作和維護成本。 在選擇如何測試應用程式時,不應該打折扣此成本,特別是考慮到對於存放庫所公開的查詢,仍可能需要針對實際資料庫進行測試。

值得注意的是,存放庫在測試之外確實具有優勢。 它們可確保所有資料存取程式碼都集中在一個位置,而不是分散在應用程式周圍,而且如果您的應用程式需要支援多個資料庫,則存放庫抽象概念對於調整跨提供者的查詢很有説明。

如需顯示使用存放庫測試的範例, 請參閱本節

整體比較

下表提供不同測試技術快速、比較的檢視,並顯示哪些功能可在哪一種方法下進行測試:

功能 記憶體中 SQLite 記憶體中的 SQLite 模擬 DbCoNtext 存放庫模式 針對資料庫進行測試
測試雙精度浮點類型 模擬/存根 Real, no double
原始 SQL? No 相依 No Yes
交易? 否(忽略) Yes Yes
提供者特定的翻譯? No Yes
確切的查詢行為? 相依 相依 相依 Yes Yes
可以在應用程式中的任何位置使用 LINQ 嗎? Yes 不* Yes

* 所有可測試的資料庫 LINQ 查詢都必須封裝在 IEnumerable-returning 存放庫方法中,才能進行擷取/模擬。

摘要

  • 我們建議開發人員對其實際生產資料庫系統執行的應用程式有良好的測試涵蓋範圍。 這可讓您確信應用程式實際上可在生產環境中運作,且設計正確,測試可以可靠地且快速地執行。 由於在任何情況下都需要這些測試,因此最好從該處開始,如有需要,請視需要使用測試雙倍新增測試。
  • 如果您決定使用測試雙精度浮點數,建議您實作存放庫模式,讓您在 EF Core 上方存根或模擬資料存取層,而不是使用假的 EF Core 提供者(Sqlite/記憶體內部)或模擬 DbSet
  • 如果因為某些原因,存放庫模式不是可行的選項,請考慮使用 SQLite 記憶體內部資料庫。
  • 請避免記憶體內部提供者進行測試, 不建議這樣做,而且只支援繼承應用程式。
  • 避免針對查詢目的進行模擬 DbSet