xUnit.net: Penyiapan global + pembongkaran?

98

Pertanyaan ini tentang kerangka pengujian unit xUnit.net .

Saya perlu menjalankan beberapa kode sebelum tes apa pun dijalankan, dan juga beberapa kode setelah semua tes selesai. Saya pikir harus ada semacam atribut atau antarmuka penanda untuk menunjukkan inisialisasi global dan kode penghentian, tetapi tidak dapat menemukannya.

Atau, jika saya menjalankan xUnit secara terprogram, saya juga dapat mencapai apa yang saya inginkan dengan kode berikut:

static void Main()
{
    try
    {
        MyGlobalSetup();
        RunAllTests();  // What goes into this method?
    }
    finally
    {
        MyGlobalTeardown();
    }
}

Adakah yang bisa memberi saya petunjuk tentang cara menjalankan beberapa kode penyiapan / pembongkaran global secara deklaratif atau terprogram?

Kodisme
sumber
1
Saya kira inilah jawabannya: stackoverflow.com/questions/12379949/…
the_joric

Jawaban:

118

Sejauh yang saya tahu, xUnit tidak memiliki titik ekstensi inisialisasi / pembongkaran global. Namun, mudah untuk membuatnya. Buat saja kelas pengujian dasar yang mengimplementasikan IDisposabledan melakukan inisialisasi Anda di konstruktor dan pembongkaran Anda dalam IDisposable.Disposemetode. Ini akan terlihat seperti ini:

public abstract class TestsBase : IDisposable
{
    protected TestsBase()
    {
        // Do "global" initialization here; Called before every test method.
    }

    public void Dispose()
    {
        // Do "global" teardown here; Called after every test method.
    }
}

public class DummyTests : TestsBase
{
    // Add test methods
}

Namun, penyiapan kelas dasar dan kode pembongkaran akan dijalankan untuk setiap panggilan. Ini mungkin bukan yang Anda inginkan, karena tidak terlalu efisien. Versi yang lebih dioptimalkan akan menggunakan IClassFixture<T>antarmuka untuk memastikan bahwa fungsi inisialisasi / pembongkaran global hanya dipanggil sekali. Untuk versi ini, Anda tidak memperluas kelas dasar dari kelas pengujian Anda tetapi mengimplementasikan IClassFixture<T>antarmuka yang Tmerujuk ke kelas fixture Anda:

using Xunit;

public class TestsFixture : IDisposable
{
    public TestsFixture ()
    {
        // Do "global" initialization here; Only called once.
    }

    public void Dispose()
    {
        // Do "global" teardown here; Only called once.
    }
}

public class DummyTests : IClassFixture<TestsFixture>
{
    public DummyTests(TestsFixture data)
    {
    }
}

Ini akan mengakibatkan konstruktor TestsFixturehanya dijalankan sekali untuk setiap kelas yang diuji. Jadi, itu tergantung pada apa yang Anda ingin pilih di antara kedua metode.

Erik Schierboom
sumber
4
Tampaknya IUseFixture tidak ada lagi, telah digantikan oleh IClassFixture.
GaTechThomas
9
Sementara ini berfungsi, saya pikir CollectionFixture dalam jawaban dari Geir Sagberg lebih cocok untuk skenario ini karena dirancang khusus untuk tujuan ini. Anda juga tidak perlu mewarisi kelas tes Anda, Anda cukup menandainya dengan [Collection("<name>")]atribut
MichelZ
8
Apakah ada cara untuk melakukan penyiapan dan pembongkaran asinkron?
Andrii
Sepertinya MS telah mengimplementasikan solusi IClassFixture juga. docs.microsoft.com/en-us/aspnet/core/test/…
lbrahim
3
XUnit menawarkan tiga opsi inisialisasi: per metode pengujian, per kelas pengujian dan mencakup beberapa kelas pengujian. Dokumentasinya ada di sini: xunit.net/docs/shared-context
GHN
49

Saya mencari jawaban yang sama, dan saat ini dokumentasi xUnit sangat membantu dalam hal cara mengimplementasikan Perlengkapan Kelas dan Perlengkapan Koleksi yang memberi pengembang berbagai fungsi penyiapan / pembongkaran di kelas atau kelompok tingkat kelas. Hal ini sejalan dengan jawaban dari Geir Sagberg, dan memberikan implementasi kerangka yang baik untuk menggambarkan seperti apa bentuknya.

https://xunit.github.io/docs/shared-context.html

Perlengkapan Koleksi Kapan digunakan: saat Anda ingin membuat konteks pengujian tunggal dan membagikannya di antara pengujian di beberapa kelas pengujian, dan membersihkannya setelah semua pengujian di kelas pengujian selesai.

Terkadang Anda ingin berbagi objek perlengkapan di antara beberapa kelas pengujian. Contoh database yang digunakan untuk perlengkapan kelas adalah contoh yang bagus: Anda mungkin ingin menginisialisasi database dengan sekumpulan data pengujian, dan kemudian membiarkan data pengujian tersebut untuk digunakan oleh beberapa kelas pengujian. Anda dapat menggunakan fitur perlengkapan koleksi xUnit.net untuk berbagi contoh objek tunggal di antara pengujian di beberapa kelas pengujian.

Untuk menggunakan perlengkapan koleksi, Anda perlu mengambil langkah-langkah berikut:

Buat kelas perlengkapan, dan letakkan kode startup di konstruktor kelas perlengkapan. Jika kelas perlengkapan perlu melakukan pembersihan, implementasikan IDisposable pada kelas perlengkapan, dan letakkan kode pembersihan di metode Dispose (). Buat kelas definisi koleksi, hiasi dengan atribut [CollectionDefinition], beri nama unik yang akan mengidentifikasi koleksi pengujian. Tambahkan ICollectionFixture <> ke kelas definisi koleksi. Tambahkan atribut [Collection] ke semua kelas pengujian yang akan menjadi bagian dari koleksi, menggunakan nama unik yang Anda berikan ke atribut [CollectionDefinition] kelas definisi koleksi pengujian. Jika kelas pengujian memerlukan akses ke instance fixture, tambahkan sebagai argumen konstruktor, dan itu akan disediakan secara otomatis. Berikut ini contoh sederhananya:

public class DatabaseFixture : IDisposable
{
    public DatabaseFixture()
    {
        Db = new SqlConnection("MyConnectionString");

        // ... initialize data in the test database ...
    }

    public void Dispose()
    {
        // ... clean up test data from the database ...
    }

    public SqlConnection Db { get; private set; }
}

[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
    // This class has no code, and is never created. Its purpose is simply
    // to be the place to apply [CollectionDefinition] and all the
    // ICollectionFixture<> interfaces.
}

[Collection("Database collection")]
public class DatabaseTestClass1
{
    DatabaseFixture fixture;

    public DatabaseTestClass1(DatabaseFixture fixture)
    {
        this.fixture = fixture;
    }
}

[Collection("Database collection")]
public class DatabaseTestClass2
{
    // ...
}

xUnit.net memperlakukan perlengkapan koleksi dengan cara yang hampir sama seperti perlengkapan kelas, kecuali bahwa umur objek perlengkapan koleksi lebih lama: itu dibuat sebelum tes apa pun dijalankan di kelas tes mana pun dalam koleksi, dan tidak akan dibersihkan hingga semua kelas pengujian dalam koleksi selesai dijalankan.

Koleksi tes juga dapat didekorasi dengan IClassFixture <>. xUnit.net memperlakukan ini seolah-olah setiap kelas tes individu dalam koleksi tes didekorasi dengan perlengkapan kelas.

Koleksi pengujian juga memengaruhi cara xUnit.net menjalankan pengujian saat menjalankannya secara paralel. Untuk informasi lebih lanjut, lihat Menjalankan Tes secara Paralel.

Catatan penting: Perlengkapan harus di rakitan yang sama dengan tes yang menggunakannya.

Larry Smith
sumber
1
"Koleksi tes juga dapat didekorasi dengan IClassFixture <>. XUnit.net memperlakukan ini seolah-olah setiap kelas tes individu dalam koleksi tes didekorasi dengan perlengkapan kelas." Adakah kesempatan saya bisa mendapatkan contoh itu? Saya tidak begitu mengerti.
rtf
@TannerFaulkner Perlengkapan kelas adalah cara untuk memiliki penyiapan dan pembongkaran level CLASS, seperti yang Anda dapatkan dengan Proyek Uji Unit .net tradisional saat Anda memiliki metode Test Initialize: [TestInitialize] public void Initialize () {
Larry Smith
Satu-satunya masalah yang saya miliki dengan ini adalah Anda perlu menghias kelas pengujian Anda dengan Collectionatribut agar penyiapan "global" terjadi. Itu berarti, jika Anda memiliki apa pun yang Anda inginkan sebelum -any- test dijalankan, Anda perlu menghias kelas -all- test dengan atribut ini. Ini terlalu rapuh menurut saya, karena lupa mendekorasi satu kelas pengujian dapat menyebabkan kesalahan yang sulit dilacak. Alangkah baiknya jika xUnit menciptakan cara untuk penyiapan dan pembongkaran yang benar-benar global.
Zodman
13

Ada solusi mudah yang mudah. Gunakan plugin Fody.ModuleInit

https://github.com/Fody/ModuleInit

Ini adalah paket nuget dan saat Anda menginstalnya, ia menambahkan file baru bernama ModuleInitializer.cs ke proyek. Ada satu metode statis di sini yang dianyam ke dalam perakitan setelah dibangun dan dijalankan segera setelah perakitan dimuat dan sebelum sesuatu dijalankan.

Saya menggunakan ini untuk membuka kunci lisensi perangkat lunak ke perpustakaan yang telah saya beli. Saya selalu lupa untuk membuka kunci lisensi di setiap tes dan bahkan lupa untuk mendapatkan tes dari kelas dasar yang akan membukanya. Percikan terang yang menulis pustaka ini, alih-alih memberi tahu Anda bahwa lisensinya dikunci, memunculkan kesalahan numerik halus yang menyebabkan pengujian gagal atau lulus padahal seharusnya tidak. Anda tidak akan pernah tahu apakah Anda telah membuka kunci perpustakaan dengan benar atau tidak. Jadi sekarang modul saya init terlihat seperti

/// <summary>
/// Used by the ModuleInit. All code inside the Initialize method is ran as soon as the assembly is loaded.
/// </summary>
public static class ModuleInitializer
{
    /// <summary>
    /// Initializes the module.
    /// </summary>
    public static void Initialize()
    {
            SomeLibrary.LicenceUtility.Unlock("XXXX-XXXX-XXXX-XXXX-XXXX");
    }
}

dan semua tes yang ditempatkan ke dalam rakitan ini akan memiliki lisensinya dibuka dengan benar untuk mereka.

bradgonesurfing
sumber
2
Ide yang solid; sayangnya tampaknya belum berfungsi dengan pengujian unit DNX.
Jeff Dunlop
12

Untuk berbagi SetUp / TearDown-code antara beberapa kelas, Anda dapat menggunakan CollectionFixture xUnit .

Mengutip:

Untuk menggunakan perlengkapan koleksi, Anda perlu mengambil langkah-langkah berikut:

  • Buat kelas perlengkapan, dan letakkan kode startup di konstruktor kelas perlengkapan.
  • Jika kelas perlengkapan perlu melakukan pembersihan, implementasikan IDisposable pada kelas perlengkapan, dan letakkan kode pembersihan di metode Dispose ().
  • Buat kelas definisi koleksi, hiasi dengan atribut [CollectionDefinition], beri nama unik yang akan mengidentifikasi koleksi pengujian.
  • Tambahkan ICollectionFixture <> ke kelas definisi koleksi.
  • Tambahkan atribut [Collection] ke semua kelas pengujian yang akan menjadi bagian dari koleksi, menggunakan nama unik yang Anda berikan ke atribut [CollectionDefinition] kelas definisi koleksi pengujian.
  • Jika kelas pengujian memerlukan akses ke instance fixture, tambahkan sebagai argumen konstruktor, dan itu akan disediakan secara otomatis.
Geir Sagberg
sumber