web-dev-qa-db-fra.com

Nettoyer et organiser des pratiques lors des tests d'intégration pour éviter les bases de données sales

Je codifie des tests en C # et je me suis installé avec cette structure:

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
}

Le concept était "chaque test nettoie le désordre que cela fait". Toutefois, Certains tests quittent la base de données sale et à l'échec des tests qui arrivent après.

Quelle est la bonne façon de faire cela? (minimiser les erreurs, minimiser le temps d'exécution)

  • Deletes everythingInsert defaultsInsert test data "Test de course?
  • Insert defaultsInsert test data "Test de course" Delete everything?

  • Actuellement:

    • (par session) Deletes everythingInsert defaults
    • (par test) Insert test data "Test de course" Delete test data
9
dialex

Outre le fait qu'il s'agit d'un test d'intégration par opposition à un test de l'unité, les opérations que vous décrivez sont généralement disponibles dans Setup et/ou Teardown méthodes. Les cadres comme Nunit permettent de décorer des méthodes de classe avec ces attributs pour indiquer si la méthode est une méthode de configuration ou une méthode de démolition.

Ensuite, vos tests devraient devenir plus propres et plus petits que la configuration et le nettoyage sont effectués en dehors du test lui-même.

Très probablement, plusieurs tests peuvent réutiliser les mêmes données de manière à ce qu'elles soient un plus et opposé à insertion/supprimer à chaque test. Revenir à -Nunit, le FixtureSetup et FixtureTeardown attributs aident à configurer les données pour plusieurs tests à la fois.

J'utiliserais un cadre de test sur un essai/attraper autant de ces fonctionnalités de test sont intégrées au cadre lui-même. Xunit, Nunit, même le cadre de test Microsoft Construit sont tous des choix solides et aidera à la configuration et au nettoyage des enregistrements de base de données de manière cohérente.

7
Jon Raynor

Le point que vous devriez viser avec de tels tests est que le plus grand nombre d'entre eux possible devraient interagir avec une maquette de la base de données, plutôt que la base de données elle-même. Le moyen standard d'atteindre cet objectif est d'injecter une couche d'accès à dB dans la logique que vous testez ici, à l'aide d'interfaces. De cette façon, le code de test peut créer des ensembles de données en mémoire avant chaque test, puis la détruire ensuite. Les tests peuvent tous courir en parallèle et ne s'affronteront pas. Cela rend vos tests plus rapidement, plus faciles à écrire et à comprendre et plus robustes.

Ensuite, vous devez tester la couche d'accès DB actuelle elle-même. Parce que vous n'aurez que quelques-uns de ces tests, ils peuvent alors, par exemple, créer une table d'essai (ou même une base de données), unique à ce test et le peupler avec des données de test. Une fois le test exécuté, toute la table d'essai/dB est alors détruite. Encore une fois, ces tests devraient pouvoir fonctionner en parallèle, il ne faut donc pas avoir un impact significatif sur l'heure d'exécution du test global.

8
David Arno

Travailler sur un serveur C # avec SQL Server and PETAPOCO , il s'agit de l'approche que nous avons prise à des données de nettoyage dans des tests unitaires.

Un test d'unité typique aurait configuré et dératrement comme suit:

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

    // ... 
}

Où plate-formeDataObject est une classe chargée de communiquer avec la base de données par exemple. Effectuer une sélection d'insertion Sélectionnez Supprimer. Tous les types * DataObject Types Inherit ServerDataObject - La classe de base contient des méthodes d'avortement, de rouler ou d'engager la transaction.

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

Tous les tests de l'unité appelleraient RollbackTransaction (), feraient finalement appelera Idbtransaction.Rollback ().

Dans les tests, nous avons trouvé la routine pour créer une nouvelle instance d'A * DataObject, créez des lignes à l'aide des instructions d'insertion, effectuez des tests sur eux (sélectionneurs, mises à jour, etc.), etc.), puis le renversement.

Nous pouvons configurer un ensemble de données de test avant que tous les tests ne soient exécutés à l'aide d'un setupfixture - une classe d'exécution une fois avant que tous les tests sont exécutés et supprimer/relancer les données en démolition après l'exécution de tous les tests.

Le gros problème avec les bases de données et les tests (unité) est que les bases de données sont si châtimeuses bien à persister les choses.

La solution habituelle est de non Utilisez une base de données réelle dans vos tests de l'unité, mais à la place de la base de données ou d'utiliser une base de données en mémoire qui peut facilement être essuyée complètement entre les tests.
[.____] uniquement lorsque vous testez le code qui interagit directement avec la base de données ou dans des tests de bout en bout serait utilisé.

5