簡體   English   中英

xUnit非靜態MemberData

[英]xUnit Non-Static MemberData

我有以下DatabaseFixture ,它已經適用於我迄今為止創建的所有測試。 我使用這個fixture進行集成測試,所以我可以在數據庫模式結構上做出真正的斷言。

public class DatabaseFixture : IDisposable
{
    public IDbConnection Connection => _connection.Value;
    private readonly Lazy<IDbConnection> _connection;

    public DatabaseFixture()
    {
        var environment = Environment.GetEnvironmentVariable("ASPNET_ENVIRONMENT") ?? "Development";
        var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("AppSettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"AppSettings.{environment}.json", optional: true, reloadOnChange: true)
            .Build();

        _connection = new Lazy<IDbConnection>(() =>
        {
            var connection = new MySqlConnection(configuration["ConnectionStrings:MyDatabase"]);
            connection.Open();
            return connection;
        });
    }

    public void Dispose()
    {
        Connection?.Dispose();
    }
}

[CollectionDefinition("Database Connection Required")]
public class DatabaseConnectionFixtureCollection : ICollectionFixture<DatabaseFixture>
{
}

我面臨的問題是我現在需要使用數據庫中的表中的每條記錄調用MyDataIsAccurate(...)等測試方法。 xUnit提供了[MemberData]屬性,這正是我需要的,但它需要一組靜態的可枚舉數據。 xUnit是否提供了一種靜態共享我的DatabaseFixture連接實例的簡潔方法,或者我只是需要將其吸收並暴露同一連接實例的靜態變量?

[Collection("Database Connection Required")]
public class MyTests
{
    protected DatabaseFixture Database { get; }

    // ERROR: Can't access instance of DatabaseFixture from static context...
    public static IEnumerable<object[]> MyData => Database.Connection.Query("SELECT * FROM table).ToList();

    public MyTests(DatabaseFixture databaseFixture)
    {
        Database = databaseFixture;
    }

    [Theory]
    [IntegrationTest]
    [MemberData(nameof(MyData))]
    public void MyDataIsAccurate(int value1, string value2, string value3)
    {
        // Assert some stuff about the data...
    }
}

您無法從提供測試用例的代碼(無論是MemberData屬性還是ClassData實現或自定義DataAttribute子類)訪問fixture。

原因

Xunit創建一個AppDomain,其中包含測試用例的所有數據。 它在測試發現時使用所有這些數據構建此AppDomain。 也就是說, IEnumerable<object[]>在構建測試程序集后坐在Xunit進程的內存中,並且它們就坐在那里等待測試運行。 這使得不同的測試用例能夠在visual studio中的測試資源管理器中顯示為不同的測試。 即使它是基於MemberDataTheory ,那些單獨的測試用例MemberData顯示為單獨的測試,因為它已經運行了該代碼,並且AppDomain等待測試運行。 另一方面,在測試RUN開始之前,不會創建燈具(無論是類燈具還是收集裝置)(您可以通過在燈具的構造函數中設置斷點並查看它何時被擊中來驗證這一點)。 這是因為它們意味着保存數據庫連接之類的東西,當它們不需要時,它們不應該在內存中保留很長時間。 因此,在創建測試用例數據時無法訪問夾具,因為尚未創建夾具。

如果我推測,我猜想Xunit的設計師會故意這樣做,並且即使測試發現加載測試用例並且因此必須首先出現的事情,它也會這樣做。沒什么大不了。 Xunit的目標不是一個方便的測試工具。 它是為了促進TDD,並且基於TDD的方法將允許任何人只使用他們的本地開發工具來獲取解決方案並運行並傳遞其他人正在運行的同一組測試,而不需要包含測試用例數據的某些記錄。預先加載到本地數據庫中。

請注意,我並不是說你不應該做你正在嘗試的事情,只是我認為Xunit的設計師會告訴你,你的測試用例和固定裝置應該填充數據庫,而不是相反。 我認為至少值得考慮這種方法是否適合你。

解決方法#1

您的靜態數據庫連接可能有效,但可能會產生意想不到的后果。 也就是說,如果數據庫中的數據在測試發現完成后發生更改(讀取:在Xunit構建測試用例之后),但在測試本身運行之前,您的測試仍將使用舊數據運行。 在某些情況下,即使再次構建項目也是不夠的 - 必須對其進行清理或重建,以便再次運行測試發現並更新測試用例。

此外,這首先打破了使用Xunit夾具的重點。 當Xunit處理了fixture時,你可以選擇:處理靜態數據庫連接(但是當你再次運行測試時它會消失,因為Xunit不一定會為下一次運行建立一個新的AppDomain)或者什么都不做,在這種情況下,它可能也是測試程序集中某個服務定位器類的靜態單例。

解決方法#2

您可以使用允許其進入夾具並檢索測試數據的數據來參數化測試。 這樣做的缺點是,您沒有將測試資源管理器或輸出中單獨的測試列表作為單獨的測試列出,正如您希望使用Theory ,但它確實在測試時加載數據而不是在設置時加載數據因此可以解決“舊數據”問題以及連接生命周期問題。

摘要

我不認為Xunit存在這樣的事情。 據我所知,您的選擇是:讓測試數據填充數據庫而不是相反,或者使用永不丟棄的靜態單例數據庫連接,或者在測試本身中提取數據。 這些都不是你所希望的“干凈”解決方案,但我懷疑你能比其中一個更好。

使用代表可以實現您想要的目標。 這個非常簡單的例子很好地解釋了它:

using System;
using System.Collections.Generic;

using Xunit;

namespace YourNamespace
{
    public class XUnitDeferredMemberDataFixture
    {
        private static string testCase1;
        private static string testCase2;

        public XUnitDeferredMemberDataFixture()
        {
            // You would populate these from somewhere that's possible only at test-run time, such as a db
            testCase1 = "Test case 1";
            testCase2 = "Test case 2";
        }

        public static IEnumerable<object[]> TestCases
        {
            get
            {
                // For each test case, return a human-readable string, which is immediately available
                // and a delegate that will return the value when the test case is run.
                yield return new object[] { "Test case 1", new Func<string>(() => testCase1) };
                yield return new object[] { "Test case 2", new Func<string>(() => testCase2) };
            }
        }

        [Theory]
        [MemberData(nameof(TestCases))]
        public void Can_do_the_expected_thing(
            string ignoredTestCaseName, // Not used; useful as this shows up in your test runner as human-readable text
            Func<string> testCase) // Your test runner will show this as "Func`1 { Method = System.String.... }"
        {
            Assert.NotNull(testCase);

            // Do the rest of your test with "testCase" string.
        }
    }
}

在OP的情況下,您可以在XUnitDeferredMemberDataFixture構造函數中訪問數據庫。

暫無
暫無

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

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