簡體   English   中英

C# 中的單元測試數據庫命令

[英]Unit Test Database Commands in C#

我目前正在嘗試為將數據插入數據庫的方法創建單元測試。 我知道我需要模擬或創建一個假類,以便我的測試數據不會添加到我的數據庫中,但我不知道如何去做。 有沒有人有想法?

這是我的控制台“用戶界面”觸及的代碼。 這是在我的“業務”C# 項目中:

        /// <summary>
        /// Gets the connection string.
        /// </summary>
        private static readonly string _connectionString = ConfigurationManager.ConnectionStrings["Database"].ConnectionString;

        /// <summary>
        /// Inserts a new admin into the Admins database table.
        /// </summary>
        /// <param name="admin">Admin record.</param>
        public static void InsertNewAdmin(Admin admin)
        {
            var adminDatabaseWriter = new AdminDatabaseWriter(_connectionString);
            adminDatabaseWriter.Insert(admin);
        }

從那里它進入我的 AdminDatabaseWriter 類並在方法“插入”中執行以下操作:

        /// <summary>
        /// Inserts a new admin into the Admins database table.
        /// </summary>
        /// <param name="admin">Admin record.</param>
        public void Insert(Admin admin)
        {
            using SqlConnection sqlConnection = new(_connectionString);
            sqlConnection.Open();

            using SqlCommand sqlCommand = DatabaseHelper.CreateNewSqlCommandWithStoredProcedure("InsertNewAdmin", sqlConnection);
            sqlCommand.Parameters.AddWithValue("@PIN", admin.PIN);
            sqlCommand.Parameters.AddWithValue("@AdminType", admin.AdminTypeCode);
            sqlCommand.Parameters.AddWithValue("@FirstName", admin.FirstName);
            sqlCommand.Parameters.AddWithValue("@LastName", admin.LastName);
            sqlCommand.Parameters.AddWithValue("@EmailAddress", admin.EmailAddress);
            sqlCommand.Parameters.AddWithValue("@Password", admin.Password);
            sqlCommand.Parameters.AddWithValue("@AssessmentScore", admin.AssessmentScore);

            var userAddedSuccessfully = sqlCommand.ExecuteNonQuery();
            sqlConnection.Close();

            if (userAddedSuccessfully < 0)
            {
                throw new AdminNotAddedToDatabaseException("User was unsuccessful at being uploaded to the database for an unknown reason.");
            }
        }

同樣,我正在嘗試對我附加的最后一段代碼進行單元測試。 測試我的代碼是否將對象實際添加到數據庫中而不實際將其添加到數據庫中的最佳方法是什么。

在為方法Insert編寫單元測試之前,您應該問自己究竟希望“單元測試”什么。 Insert返回任何內容 (void),因此沒有要測試的返回值。 但是,它在滿足某些條件時會拋出異常,並且它也有副作用(調用ExecuteNonQuery將一些數據放入數據庫中)。

無論您決定測試什么場景(無論是拋出異常還是出現副作用),您都應該告訴您的測試框架,當它到達發生副作用的行時,它應該用另一個實際上什么都不做的操作替換它(不僅如此實際數據庫不會變得“臟”,而且因為外部依賴項可能會消耗時間,甚至更糟,使您的測試失敗,因為它們本身失敗了,並且基本上我們不關心在上下文中的此類依賴項我們測試的對象)。

所有標准測試框架/庫都提供了對異常和副作用進行斷言的工具,但請注意,模擬您的副作用(通常是模擬依賴項)不僅在您針對返回值或異常進行測試時必不可少,而且在您進行測試時也很重要希望驗證副作用本身是否發生。 因為無論哪種方式,我們都希望防止外部依賴項干擾測試。

無論您使用什么測試框架,在您的產品代碼中使用接口通常是首選,以促進您的應用程序的靈活性和可測試性,並且它還可以讓您模擬“副作用”方法,例如ExecuteNonQuery (即ISqlCommand而不是SqlCommand )。 這是因為當您指示模擬接口方法時,框架會創建一個新的虛擬類來實現相同的接口。

總而言之,可以通過以下方式測試對象是否“添加”到數據庫中:

  1. 更改代碼以使用接口(ISqlConnection、ISqlCommand)。
  2. 模擬所需接口的方法( OpenExecuteNonQueryClose )。
  3. 執行Insert
  4. 驗證ExecuteNonQuery是否已被調用。

(是否正確添加了對象是另一回事,這可能需要集成測試。)

問題中提供的這段代碼具有三個職責:

  1. 創建SqlCommand並填充參數;
  2. 執行SqlCommand最終會有一些副作用;
  3. 檢查執行結果,如果值不是預期的,則拋出。

顯然,這些職責中的每一個都需要進行測試,問題是對每個職責使用相同的測試方法幾乎沒有意義。 雖然您可以對第 1 點和第 3 點(下面有更多內容)進行單元測試,但嘗試為第 2 點這樣做是無益的。 SqlCommand執行涉及與外部系統(即數據庫)的交互,即您需要實現某種數據庫組件隔離。 而且你沒有那么多選擇來這樣做,而每一個都遠非理想。

選項是(至少我知道的那些):

  1. 針對抽象編寫代碼(如另一個答復中建議的ISqlCommand )並模擬它們(使用諸如NSubstitute 之類的東西),以便您在嘗試訪問真實數據庫時不做任何事情。 您只需要在模擬上斷言,以查看是否以正確的順序使用預期的參數調用了正確的方法。 這種方法在幾個方面是不好的:
    • 數據庫交互未經測試,但可能隱藏了錯誤(如錯誤的存儲過程名稱或實現或無效參數);
    • 測試變得脆弱,因為通過使用模擬,您可以揭示方法的實現細節。 例如,方法調用重新排序或添加在測試方法 api 級別不可見的附加參數等簡單的事情可能會使測試失敗,而更改本身對於業務邏輯來說可能是絕對正確的,因此不應導致任何測試失敗。
  2. 在內存中使用一些數據庫替換,如SqliteEF Core
    • 盡管這種方法比前一種方法更好 imo,因為您的數據庫或多或少與真實數據庫相似,但由於缺乏功能和行為差異,仍然無法正確測試數據庫交互。

解決該問題的另一種方法是針對真實數據庫(不是生產數據庫,而是專門為測試准備的)編寫單元測試,而是編寫集成測試。 您只需要在測試執行之前/之后清理它。 我個人認為這種方法最適合用於測試數據庫交互,因為您正在最接近生產環境的環境中測試應用程序行為。 缺點是測試設置有點復雜,測試運行很可能需要更多時間。 Reseed (我正在編寫它)或Respawn 之類的庫可能有助於簡化設置並優化執行速度。

現在回到職責 1 和 3 的單元測試。應該可以通過稍微更改您的方法設計來為它們編寫單元測試。 您可能會提取一些額外的方法,每個方法都將顯式處理每個職責。 所以最初的Insert現在可以看作是三個方法的組合:

  1. 用於准備SqlCommand參數的CreateParameters – 您將能夠斷言純單元測試正確填充了參數;
  2. Execute以使用真實數據庫執行命令——這可以在集成測試中進行測試;
  3. 如果用戶創建失敗, AssertUserCreated會拋出異常——同樣,純單元測試就足夠了。

這將使您能夠達到 100% 的測試覆蓋率,而這並不是必需的。 如果參數創建邏輯和存儲過程結果驗證邏輯像現在這樣簡單,則第 1 點和第 3 點可以不進行測試。

當然,設計還可以進一步改進,使其不僅更具可測試性,而且更具可讀性,這只是一個想法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM