Pulisci e organizza le pratiche durante i test di integrazione per evitare database sporchi

7

Sto test di codifica in C # e ho optato per questa struttura:

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
}

Il concetto era "ogni test ripulisce il disordine che crea". Tuttavia, alcuni test stanno lasciando il database sporco e fallendo i test che verranno dopo.

Qual è il modo giusto per farlo? (minimizza gli errori, minimizza i tempi di esecuzione)

  • Elimina tutto » Inserisci valori predefiniti » Inserisci dati test »Esegui test?
  • Inserisci valori predefiniti » Inserisci dati test » Esegui test » Elimina tutto ?

  • Attualmente :

    • (per sessione) Elimina tutto » Inserisci valori predefiniti
    • (per test) Inserisci dati test »Esegui test» Elimina dati test
posta DiAlex 28.01.2016 - 18:36
fonte

4 risposte

6

Oltre al fatto che si tratta di un test di integrazione rispetto a un test unitario, le operazioni che descrivi generalmente vanno in Setup e / o Teardown metodi. Framework come nUnit consentono di decorare i metodi di classe con questi attributi per indicare se il metodo è un metodo di installazione o un metodo di demolizione.

Quindi i test dovrebbero diventare più puliti e più piccoli man mano che l'installazione e la pulizia vengono eseguite al di fuori del test stesso.

Molto probabilmente più test possono riutilizzare gli stessi dati in modo tale che sia un vantaggio e non l'inserimento / rimozione su ogni test. Tornando a nUnit , gli attributi FixtureSetup e FixtureTeardown aiutano a configurare i dati per più test contemporaneamente.

Vorrei usare un framework di test su un try / catch visto che molte di queste funzionalità di test sono incorporate nel framework stesso. xUnit, nUnit, anche il framework di testing di Microsoft sono tutte scelte solide e aiuteranno a configurare e ripulire i record del database in modo coerente.

    
risposta data 28.01.2016 - 23:32
fonte
7

Il punto a cui dovresti mirare con questi test è che molti di essi dovrebbero interagire con una simulazione del database, piuttosto che con il database stesso. Il modo standard per ottenere ciò è iniettare un livello di accesso DB nella logica che si sta testando qui, usando le interfacce. In questo modo, il codice di test può creare set di dati in memoria prima di ogni test e poi eliminarli in seguito. I test possono quindi essere eseguiti in parallelo e non si influenzeranno a vicenda. Ciò rende i tuoi test più veloci, più facili da scrivere e da comprendere e più robusti.

Quindi è necessario testare lo stesso livello di accesso al DB stesso. Poiché sono disponibili solo alcuni di questi test, possono, ad esempio, creare una tabella di test (o anche un database), univoci per quel test, e popolarla con i dati di test. Dopo l'esecuzione del test, l'intera tabella di test / DB viene quindi distrutta. Di nuovo, questi test dovrebbero essere in grado di funzionare in parallelo, quindi non dovrebbero avere un impatto significativo sul tempo di esecuzione complessivo del test.

    
risposta data 28.01.2016 - 19:21
fonte
5

Il grosso problema con i database e i test (unit-) è che i database sono così dannatamente bravi a persistere.

La solita soluzione è quella di non utilizzare un vero database nei test delle unità, ma invece prendere in giro il database o utilizzare un database in memoria che può essere facilmente cancellato tra un test e l'altro. < br> Solo durante il test del codice che interagisce direttamente con il database o nei test end-to-end verrà utilizzato il database effettivo.

    
risposta data 28.01.2016 - 19:23
fonte
4

Lavorando su un server C # con SQL Server e PetaPoco , questo è l'approccio che abbiamo adottato per ripulire i dati nell'unità test.

Un test unitario tipico avrebbe Setup e Teardown come segue:

[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();
    }

    // ... 
}

Dove PlatformDataObject è una classe responsabile della comunicazione con il database, ad es. eseguendo Seleziona Inserisci Elimina aggiornamenti. Tutti i tipi * DataObject ereditano ServerDataObject - la classe base ha metodi per l'interruzione, il rollback o il commit della transazione.

/// <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();
    }
}

Tutti i test unitari chiamerebbero RollbackTransaction (), in definitiva chiamerebbero IDbTransaction.Rollback ().

Nei test abbiamo trovato che routine creare una nuova istanza di * DataObject, creare alcune righe usando le istruzioni Insert, eseguire test su di esse (Seleziona, Aggiorna ecc.) e quindi eseguire il rollback.

Potremmo impostare una serie di dati di test prima di eseguire tutti i test utilizzando un SetUpFixture - una classe viene eseguita una volta prima dell'esecuzione di tutti i test ed elimina / ripristina i dati in teardown dopo l'esecuzione di tutti i test.

    
risposta data 29.01.2016 - 17:45
fonte

Leggi altre domande sui tag