web-dev-qa-db-ja.com

MemberDataテストが多数ではなく1つのテストとして表示される

[Theory][InlineData]と一緒に使用すると、提供されるインラインデータの各項目のテストが作成されます。ただし、[MemberData]を使用すると、1つのテストとして表示されます。

[MemberData]テストを複数のテストとして表示する方法はありますか?

32
NPadrutt

私は自分のプロジェクトでこれを理解しようと多くの時間を費やしました。 これに関連するGithubディスカッション @NPadrutt自身が多くのことを助けましたが、それでも混乱を招きました。

Tl; drは次のとおりです:[MemberInfo]は、各テストに提供されたオブジェクトを完全にシリアライズおよびデシリアライズできない限り、単一のグループテストを報告します。 IXunitSerializableを実装しています。


背景

私自身のテスト設定は次のようなものでした:

public static IEnumerable<object[]> GetClients()
{
    yield return new object[] { new Impl.Client("clientType1") };
    yield return new object[] { new Impl.Client("clientType2") };
}

[Theory]
[MemberData(nameof(GetClients))]
public void ClientTheory(Impl.Client testClient)
{
    // ... test here
}

テストは2回実行され、期待どおりに[MemberData]のオブジェクトごとに1回実行されました。 @NPadruttが経験したように、テストエクスプローラーには2つではなく1つだけのアイテムが表示されました。これは、提供されたオブジェクトImpl.Clientが、xUnitがサポートするいずれのインターフェースでもシリアル化できなかったためです(これについては後で詳しく説明します)。

私の場合、テストの懸念をメインコードに流したくありませんでした。実際のクラスの周りに、xUnitランナーをだまして直列化できると思わせるような薄いプロキシを書けると思ったのですが、認めざるを得ないほど長い間戦い続けた後、私が理解していない部分が:

オブジェクトは、順列をカウントするためにディスカバリー中にシリアル化されるだけではありません。また、各オブジェクトは、テストの開始時にテスト実行時に逆シリアル化されます

したがって、[MemberData]で提供するオブジェクトはすべて、完全なラウンドトリップ(逆)シリアル化をサポートする必要があります。これは今では当たり前のようですが、理解しようとしているときにドキュメントを見つけることができませんでした。


ソリューション

  • すべてのオブジェクト(およびそのオブジェクトに含まれる可能性のある非プリミティブ)が完全にシリアル化および逆シリアル化できることを確認してください。 xUnitのIXunitSerializableを実装すると、xUnitがシリアル化可能なオブジェクトであることを認識できます。

  • 私の場合のように、メインコードに属性を追加したくない場合、1つの解決策は、実際のクラスの再作成に必要なすべてを表すことができる、テスト用の薄いシリアル化可能なビルダークラスを作成することです。動作させた後の上記のコードは次のとおりです。

TestClientBuilder

public class TestClientBuilder : IXunitSerializable
{
    private string type;

    // required for deserializer
    public TestClientBuilder()
    {
    }

    public TestClientBuilder(string type)
    {
        this.type = type;
    }

    public Impl.Client Build()
    {
        return new Impl.Client(type);
    }

    public void Deserialize(IXunitSerializationInfo info)
    {
        type = info.GetValue<string>("type");
    }

    public void Serialize(IXunitSerializationInfo info)
    {
        info.AddValue("type", type, typeof(string));
    }

    public override string ToString()
    {
        return $"Type = {type}";
    }
}

テスト

public static IEnumerable<object[]> GetClients()
{
    yield return new object[] { new TestClientBuilder("clientType1") };
    yield return new object[] { new TestClientBuilder("clientType2") };
}

[Theory]
[MemberData(nameof(GetClients))]
private void ClientTheory(TestClientBuilder clientBuilder)
{
    var client = clientBuilder.Build();
    // ... test here
}

もうターゲットオブジェクトが注入されないのは少しイライラしますが、ビルダーを呼び出すための追加の1行のコードにすぎません。そして、私のテストは合格(そして2回表示されます!)なので、文句はありません。

25
Nate Barbettini

MemberDataは、object []のIEnumerableを返すプロパティまたはメソッドを操作できます。このシナリオでは、歩留まりごとに個別のテスト結果が表示されます。

public class Tests
{ 
    [Theory]
    [MemberData("TestCases", MemberType = typeof(TestDataProvider))]
    public void IsLargerTest(string testName, int a, int b)
    {
        Assert.True(b>a);
    }
}

public class TestDataProvider
{
    public static IEnumerable<object[]> TestCases()
    {
        yield return new object[] {"case1", 1, 2};
        yield return new object[] {"case2", 2, 3};
        yield return new object[] {"case3", 3, 4};
    }
}

ただし、テストケースの数に関係なく、複雑なカスタムオブジェクトを渡す必要があるとすぐに、テスト出力ウィンドウには1つのテストのみが表示されます。これは理想的な動作ではなく、どのテストケースが失敗しているのかをデバッグする際には非常に不便です。回避策は、IXunitSerializableから派生する独自のラッパーを作成することです。

public class MemberDataSerializer<T> : IXunitSerializable
    {
        public T Object { get; private set; }

        public MemberDataSerializer()
        {
        }

        public MemberDataSerializer(T objectToSerialize)
        {
            Object = objectToSerialize;
        }

        public void Deserialize(IXunitSerializationInfo info)
        {
            Object = JsonConvert.DeserializeObject<T>(info.GetValue<string>("objValue"));
        }

        public void Serialize(IXunitSerializationInfo info)
        {
            var json = JsonConvert.SerializeObject(Object);
            info.AddValue("objValue", json);
        }
    }

これで、カスタムオブジェクトをXunit Theoriesのパラメーターとして使用でき、テストランナーウィンドウでそれらを独立した結果として表示/デバッグできます。

public class UnitTest1
{
    [Theory]
    [MemberData("TestData", MemberType = typeof(TestDataProvider))]
    public void Test1(string testName, MemberDataSerializer<TestData> testCase)
    {
        Assert.Equal(1, testCase.Object.IntProp);
    }
}

public class TestDataProvider
{
    public static IEnumerable<object[]> TestData()
    {
        yield return new object[] { "test1", new MemberDataSerializer<TestData>(new TestData { IntProp = 1, StringProp = "hello" }) };
        yield return new object[] { "test2", new MemberDataSerializer<TestData>(new TestData { IntProp = 2, StringProp = "Myro" }) };      
    }
}

public class TestData
{
    public int IntProp { get; set; }
    public string StringProp { get; set; }
}

お役に立てれば。

12
user1807319

私の最近のプロジェクトで私は非常に同じ問題を経験し、いくつかの研究の後、私が思いついた解決策は次のとおりです:

FactAttributeを拡張するカスタムMyTheoryAttributeを実装し、MyTheoryDiscovererを実装してIXunitTestCaseDiscovererを実装し、TestMethodTestCaseを拡張してIXunitTestCaseを実装するカスタムMyTestCaseをいくつか実装します。カスタムテストケースはMyTheoryDiscovererによって認識され、列挙された理論のテストケースを、Xunitによってネイティブにシリアル化されておらず、IXunitSerializableを実装していない場合でも、Xunitフレームワークから見える形式でカプセル化するために使用する必要があります。

最も重要なことテスト中の貴重なコードを変更する必要はありません

少し手間はかかりますが、すでに私が作成しており、MITライセンスで自由に使用できます。 DjvuNet プロジェクトの一部です。 GitHubでホストされています。

Xunitサポートコードを含む関連フォルダーへの直接リンクは次のとおりです。

DjvuNetテストサポートコード

これを使用するには、このファイルを使用して個別のアセンブリを作成するか、それらをテストプロジェクトに直接含めます。

使用方法はXunit TheoryAttributeとまったく同じで、ClassDataAttributeとMemberDataAttributeの両方がサポートされています

[DjvuTheory]
[ClassData(typeof(DjvuJsonDataSource))]
public void InfoChunk_Theory(DjvuJsonDocument doc, int index)
{
    // Test code goes here
}


[DjvuTheory]
[MemberData(nameof(BG44TestData))]
public void ProgressiveDecodeBackground_Theory(BG44DataJson data, long length)
{
    // Test code goes here
}

クレジットは他の開発者にも当てはまりますが、残念ながらgithubで彼のリポジトリを見つけることができません

3

現時点では、カスタムクラスがToString()をオーバーライドすると、ReSharperはすべてのMemberDataテストをカスタムパラメータとともに表示できます。

例えば ​​:

_public static TheoryData<Permission, Permission, Permission> GetAddRuleData()
{
    var data = new TheoryData<Permission, Permission, Permission>
    {
        {
            new Permission("book", new[] {"read"}, null),
            new Permission("book", new[] {"delete"}, new[] {"2333"}),
            new Permission("book", new[] {"delete", "read"}, new[] {"*", "2333"})
        },
        {
            new Permission("book", new[] {"read"}, null),
            new Permission("music", new[] {"read"}, new[] {"2333"}), new Permission
            {
                Resources = new Dictionary<string, ResourceRule>
                {
                    ["book"] = new ResourceRule("book", new[] {"read"}, null),
                    ["music"] = new ResourceRule("music", new[] {"read"}, new[] {"2333"}),
                }
            }
        }
    };
    return data;
}
_

PermissionToString()をオーバーライドし、ReSharper Test Session Explorerで次のようにします。

xunitR#

1
Tao Zhu