Bersihkan & Atur praktik selama pengujian integrasi untuk menghindari database yang kotor

9

Saya mengkode tes dalam C # dan saya puas dengan struktur ini:

try
{
    // ==========
    // ARRANGE
    // ==========

    // Insert into the database all test data I'll need during the test

    // ==========
    // ACT
    // ==========

    // Do what needs to be tested

    // ==========
    // ASSERT
    // ==========

    // Check for correct behavior
}
finally
{
    // ==========
    // CLEANUP
    // ==========

    // Inverse of ARRANGE, delete the test data inserted during this test
}

Konsepnya adalah "setiap tes membersihkan kekacauan yang terjadi". Namun, beberapa tes meninggalkan database kotor dan gagal tes yang terjadi setelahnya.

Apa cara yang benar untuk melakukan ini? (meminimalkan kesalahan, meminimalkan waktu berjalan)

  • Deletes everything» Insert defaults» Insert test data»Jalankan tes?
  • Insert defaults» Insert test data» Jalankan tes » Delete everything?

  • Saat ini :

    • (per sesi) Deletes everything»Insert defaults
    • (per tes) Insert test data»Jalankan tes»Delete test data
dialex
sumber

Jawaban:

7

Selain fakta bahwa ini adalah tes integrasi yang bertentangan dengan tes unit, operasi yang Anda gambarkan biasanya masuk Setupdan / atau Teardownmetode. Kerangka kerja seperti nUnit memungkinkan seseorang untuk menghias metode kelas dengan atribut-atribut ini untuk menunjukkan apakah metode tersebut adalah metode pengaturan atau metode teardown.

Maka tes Anda harus menjadi lebih bersih dan lebih kecil karena pengaturan dan pembersihan dilakukan di luar tes itu sendiri.

Kemungkinan beberapa tes dapat menggunakan kembali data yang sama sehingga merupakan nilai plus serta bertentangan dengan memasukkan / menghapus pada setiap tes. Kembali ke nUnit , atribut FixtureSetupdan FixtureTeardownmembantu mengatur data untuk beberapa tes sekaligus.

Saya akan menggunakan kerangka uji coba / tangkapan karena banyak dari fitur pengujian ini dibangun ke dalam kerangka itu sendiri. xUnit, nUnit, bahkan kerangka pengujian bawaan Microsoft semuanya merupakan pilihan yang solid dan akan membantu pengaturan dan pembersihan catatan basis data secara konsisten.

Jon Raynor
sumber
8

Poin yang harus Anda tuju dengan tes semacam itu adalah sebanyak mungkin dari mereka yang berinteraksi dengan tiruan dari database, bukan dari database itu sendiri. Cara standar untuk mencapai ini adalah dengan menyuntikkan lapisan akses DB ke logika yang Anda uji di sini, menggunakan antarmuka. Dengan begitu, kode pengujian dapat membuat set data dalam memori sebelum setiap pengujian dan kemudian membuangnya setelahnya. Semua pengujian kemudian dapat berjalan secara paralel dan tidak akan saling mempengaruhi. Ini membuat tes Anda lebih cepat, lebih mudah untuk menulis dan memahami dan lebih kuat.

Maka Anda perlu menguji lapisan akses DB yang sebenarnya itu sendiri. Karena Anda hanya akan memiliki beberapa tes ini, mereka kemudian dapat, misalnya, membuat tabel tes (atau bahkan database), unik untuk tes itu, dan mengisi dengan data uji. Setelah tes berjalan, seluruh tabel uji / DB kemudian dihancurkan. Sekali lagi, tes ini harus dapat berjalan secara paralel, sehingga seharusnya tidak memiliki dampak yang signifikan pada keseluruhan waktu pelaksanaan tes.

David Arno
sumber
Nah itu sedikit perubahan bagi kami sekarang (kami memulai pengujian unit 3 bulan lalu). Dengan asumsi bahwa, untuk saat ini, kami akan menggunakan database nyata untuk tes, apa standar / urutan aman untuk melakukan ini - hapus semuanya, masukkan semuanya, dan kemudian jalankan tes?
dialex
1
Jika restrukturisasi tidak mungkin dilakukan, maka - dalam kasus Anda - @ JonRaynor memberikan pilihan terbaik Anda.
David Arno
5

Masalah besar dengan database dan (unit) tes adalah bahwa database sangat baik dalam hal-hal yang bertahan.

Solusi yang biasa adalah tidak menggunakan database aktual dalam unit-tes Anda, tetapi malah mengejek database atau menggunakan database di-memori yang dapat dengan mudah dihapus sepenuhnya di antara tes.
Hanya ketika menguji kode yang secara langsung berinteraksi dengan basis data, atau dalam pengujian ujung ke ujung, basis data yang sebenarnya akan digunakan.

Bart van Ingen Schenau
sumber
5

Bekerja pada C # Server dengan SQL Server dan PetaPoco , ini adalah pendekatan yang kami ambil untuk membersihkan data dalam Tes Unit.

Unit test yang khas akan memiliki Setup dan Teardown sebagai berikut:

[TestFixture]
internal class PlatformDataObjectTests
{
    private IDatabaseConfiguration _dbConfig;
    private Database _pocoDatabase;
    private PlatformDataObject _platformDto;

    [SetUp]
    public void Setup()
    {
        _dbConfig = new CommonTestsAppConfig().GetDatabaseConfiguration();
        _pocoDatabase = new Database(_dbConfig.ConnectionString, SqlClientFactory.Instance);
        _platformDto = new PlatformDataObject(_pocoDatabase);
        _platformDto.BeginTransaction();
    }

    [TearDown]
    public void TearDown()
    {
        Console.WriteLine("Last Sql: {0}", _pocoDatabase.LastCommand);

        _platformDto.RollbackTransaction();
        _platformDto.Dispose();
    }

    // ... 
}

Di mana PlatformDataObject adalah kelas yang bertanggung jawab untuk berkomunikasi dengan database misalnya melakukan Pilih Sisipkan Perbarui Hapus. Semua tipe * DataObject mewarisi ServerDataObject - kelas dasar memiliki metode untuk membatalkan, memutar kembali atau melakukan transaksi.

/// <summary>
/// A Data-Transfer Object which allows creation and querying of Platform types from the database
/// </summary>
[ExportType(typeof(IPlatformDataObject))]
public class PlatformDataObject : ServerDataObject, IPlatformDataObject
{
    private static readonly ILog Log = LogManager.GetLogger(typeof (ProductDataObject));

    private const string PlatformTable = "t_Platform";

    public PlatformDataObject(IPocoDatabase pocoDatabase) : base(pocoDatabase)
    {
    }

    ... 
}

/// <summary>
/// A base Data-Transfer Object type
/// </summary>
public abstract class ServerDataObject : IServerDataObject
{
    protected const string Star = "*";

    private readonly IPocoDatabase _pocoDatabase;

    public ServerDataObject(IPocoDatabase pocoDatabase)
    {
        _pocoDatabase = pocoDatabase;
    }

    public string LastCommand
    {
        get { return PocoDatabase.LastCommand; }
    }

    public IPocoDatabase PocoDatabase
    {
        get { return _pocoDatabase; }
    }

    public int TransactionDepth
    {
        get { return _pocoDatabase.TransactionDepth; }
    }

    public bool TransactionAborted { get; private set; }

    public void BeginTransaction()
    {
        _pocoDatabase.BeginTransaction();
    }

    public void AbortTransaction()
    {
        _pocoDatabase.AbortTransaction();
    }

    public void RollbackTransaction()
    {
        TransactionAborted = true;
    }

    public virtual void Dispose()
    {
        if (TransactionAborted)
            _pocoDatabase.AbortTransaction();
        else
            _pocoDatabase.CompleteTransaction();
    }
}

Semua tes unit akan memanggil RollbackTransaction (), pada akhirnya akan memanggil IDbTransaction.Rollback ().

Dalam pengujian kami menemukan rutin untuk membuat instance baru dari * * DataObject, membuat beberapa baris menggunakan pernyataan Sisipkan, melakukan tes pada mereka (Memilih, Pembaruan dll ...) dan kemudian memutar kembali.

Kami dapat mengatur satu set data uji sebelum semua tes dijalankan menggunakan SetUpFixture - kelas yang dijalankan sekali sebelum semua tes dijalankan, dan menghapus / mengembalikan data dalam teardown setelah semua tes dijalankan.

Andrew Burnett-Thompson
sumber