Saya bertanggung jawab atas pengujian perangkat lunak lama yang dikembangkan dalam C dan C# yang dikelola oleh tim saya. Tim asli menggunakan NSubstitute 3.1 untuk membuat uji ganda untuk delegasi guna melakukan pengujian unit API untuk bagian C#. Inilah salah satu tes ganda tersebut, di mana detail yang tidak relevan telah dihilangkan:

private static byte[] MockSelectByAidWithoutData(ushort retVal)
{
    var expectedIn= "FFFEFDFCFB".HexToBytes();
    var expectedOut= "010203040506070809".HexToBytes();

    var fake = Substitute.For<SomeDelegate>();
    fake(Arg.Is<byte[]>(x => expectedIn.SequenceEqual(x.Take(expectedIn.Length))),
            Arg.Is(0x00),
            Arg.Is(expectedIn.Length),
            Arg.Any<int>(),
            Arg.Any<int>(),
            out int outputLength)
        .Returns(x =>
            {
                expectedOut.CopyTo((Array)x[0], 0);
                x[5] = expectedOut.Length;
                return retVal;
            }
        );
    Mediator.GetInstance().Delegate = fake;
    return expectedOut;
}

Sekarang, jika delegasi palsu dipanggil dengan argumen yang cocok dengan apa yang ditentukan dalam panggilan fake(), ia mengembalikan nilai retVal dan semua orang senang. Namun, jika beberapa nilai tidak cocok, ia mengembalikan nol. Karena nol adalah nilai yang valid tetapi salah, eksekusi berlanjut dan saya mendapatkan kesalahan yang bukan merupakan akar penyebab masalah yang saya uji (yaitu keluaran buruk ketika masalahnya sebenarnya adalah masukan buruk)

Saya mencari cara untuk:

  • tentukan perilaku "tangkap semua" untuk nilai yang tidak sesuai dengan harapan, atau
  • dapatkan pengecualian jika argumen tidak sesuai dengan harapan

Sehingga kasus uji akan segera gagal setelah menerima masukan yang salah dengan pesan yang bermakna dan tanpa memicu perilaku lebih lanjut yang hanya akan mencemari hasil pengujian.

Terima kasih sebelumnya,

DeK

P.S. Saya mungkin dapat beralih dengan aman ke versi NSubstitute yang lebih baru jika itu benar-benar diperlukan.

0
Dek Dekku 6 Mei 2021, 20:47

1 menjawab

Jawaban Terbaik

tentukan perilaku "tangkap semua" untuk nilai yang tidak sesuai dengan harapan

Saya pikir saya telah menemukan cara Anda dapat melakukan ini. Jika Anda pertama kali mematikan kasus "tangkap semua"/kegagalan untuk semua argumen, Anda kemudian dapat mematikan panggilan yang lebih spesifik. NSubstitute akan mencoba mencocokkan spesifikasi terbaru yang diberikan, kembali ke nilai stub sebelumnya.

Berikut adalah contoh.

Perhatikan bahwa ini menggunakan Configure dari NSubstitute.Extensions namespace diperkenalkan di NSubstitute 4.x. Ini tidak sepenuhnya diperlukan karena NSubstitute akan secara otomatis menganggap Anda mengonfigurasi panggilan jika Anda menggunakan pencocokan argumen, tetapi ini adalah pola yang baik untuk digunakan saat mengonfigurasi panggilan yang tumpang tindih seperti ini.

using NSubstitute;
using NSubstitute.Extensions; // required for Configure()

public class Thing {
    public string Id { get; set; }
}

public interface ISample {
    int Example(Thing a, string b);
}

public class UnexpectedCallException : Exception { }

[Fact]
public void ExampleOfStubOneCallButFailOthers() {
    var sub = Substitute.For<ISample>();

    // Catch all case:
    sub.Example(null, null).ReturnsForAnyArgs(x => throw new UnexpectedCallException());

    // Specific case. We use Configure from NSubstitute.Extensions to
    // be able to stub this without getting an UnexpectedCallException.
    // Not strictly necessary here as we're using argument matchers so NSub
    // already knows we're configuring a call, but it's a good habit to get into.
    // See: https://nsubstitute.github.io/help/configure/
    sub.Configure()
        .Example(Arg.Is<Thing>(x => x.Id == "abc"), Arg.Any<string>())
        .Returns(x => 42);

    // Example of non-matching call:
    Assert.Throws<UnexpectedCallException>(() =>
        sub.Example(new Thing { Id = "def" }, "hi")
    );

    // Example of matching call:
    Assert.Equal(42, sub.Example(new Thing { Id = "abc" }, "hello"));
}

Anda dapat memperluas ini untuk memasukkan informasi tentang argumen yang tidak cocok, tetapi itu akan menjadi sedikit pekerjaan khusus. Jika Anda melihat beberapa kode pemformatan argumen NSubstitute yang mungkin dapat digunakan kembali untuk membantu dalam hal ini.


Perbarui untuk menyertakan contoh delegasi

Saya baru saja menjalankan ini dengan delegasi dan juga melewati:

public delegate int SomeDelegate(Thing a, string b);

[Fact]
public void ExampleOfStubOneDelegateCallButFailOthers() {
    var sub = Substitute.For<SomeDelegate>();
    sub(null, null).ReturnsForAnyArgs(x => throw new UnexpectedCallException());
    sub.Configure()
        .Invoke(Arg.Is<Thing>(x => x.Id == "abc"), Arg.Any<string>())
        .Returns(x => 42);
    Assert.Throws<UnexpectedCallException>(() => sub(new Thing { Id = "def" }, "hi"));
    Assert.Equal(42, sub(new Thing { Id = "abc" }, "hello"));
}
1
David Tchepak 12 Mei 2021, 23:47