174 votes

Comment, à l’unité de tester un objet avec des requêtes de base de données

J’ai entendu dire que les tests unitaires sont « génial », « vraiment cool » et « toutes sortes de bonnes choses », mais 70 % ou plus de mes fichiers impliquent un accès base de données (certains lire et certains écrivent) et je ne sais pas comment écrire un test unitaire pour ces fichiers.

J’utilise PHP et Python, mais je pense que c’est une question qui s’applique à la plupart/toutes les langues qui utilisent l’accès de la base de données.

90voto

Doug R Points 4034

Je suggère de se moquant de vos appels à la base de données. On se moque de sont essentiellement des objets qui ressemblent à l'objet que vous essayez d'appeler une méthode, dans le sens qu'ils ont les mêmes propriétés, les méthodes, etc. disponible à l'appelant. Mais au lieu d'effectuer ce qu'ils sont programmés pour faire lorsqu'une méthode est appelée, il ignore que, globalement, et retourne un résultat. Ce résultat est généralement que vous avez défini à l'avance.

Afin de mettre en place vos objets de moquerie, vous avez probablement besoin d'utiliser une sorte d'inversion de contrôle et l'injection de dépendance modèle, comme dans le pseudo-code:

class Bar
{
    private FooDataProvider _dataProvider;

    public instantiate(FooDataProvider dataProvider) {
        _dataProvider = dataProvider;
    }

    public getAllFoos() {
        // instead of calling Foo.GetAll() here, we are introducing an extra layer of abstraction
        return _dataProvider.GetAllFoos();
    }
}

class FooDataProvider
{
    public Foo[] GetAllFoos() {
        return Foo.GetAll();
    }
}

Maintenant, dans votre unité de test, vous créer une maquette de FooDataProvider, qui vous permet d'appeler la méthode GetAllFoos sans avoir à toucher la base de données.

class BarTests
{
    public TestGetAllFoos() {
        // here we set up our mock FooDataProvider
        mockRepository = MockingFramework.new()
        mockFooDataProvider = mockRepository.CreateMockOfType(FooDataProvider);

        // create a new array of Foo objects
        testFooArray = new Foo[] {Foo.new(), Foo.new(), Foo.new()}

        // the next statement will cause testFooArray to be returned every time we call FooDAtaProvider.GetAllFoos,
        // instead of calling to the database and returning whatever is in there
        // ExpectCallTo and Returns are methods provided by our imaginary mocking framework
        ExpectCallTo(mockFooDataProvider.GetAllFoos).Returns(testFooArray)

        // now begins our actual unit test
        testBar = new Bar(mockFooDataProvider)
        baz = testBar.GetAllFoos()

        // baz should now equal the testFooArray object we created earlier
        Assert.AreEqual(3, baz.length)
    }
}

Une commune se moquant de scénario, en un mot. Bien sûr, vous aurez probablement envie de test de l'unité réelle de la base de données des appels trop, pour laquelle vous avez besoin de frapper la base de données.

29voto

Sean Chambers Points 3159

Idéalement, vos objets devrait être persistant dans l'ignorance. Par exemple, vous devez avoir une couche d'accès aux données", qui vous permettrait de faire des demandes pour, que serait le retour des objets. De cette façon, vous pouvez laisser une partie de vos tests unitaires, ou de les tester dans l'isolement.

Si vos objets sont étroitement couplées à votre couche de données, il est difficile de faire le bon tests unitaires. la première partie de l'unité de test, est "l'unité". Toutes les unités doivent pouvoir être testés séparément.

Dans mes projets c#, j'utilise NHibernate complètement séparée de la couche de Données. Mes objets de vivre dans le domaine de base du modèle et sont accessibles à partir de mon application de la couche. L'application de la couche parle à la fois de la couche de données et le modèle du domaine de la couche.

La couche application est aussi parfois appelée "Couche".

Si vous utilisez PHP, créer un ensemble de classes pour SEULEMENT accès aux données. Assurez-vous que vos objets n'ont aucune idée de la façon dont ils sont persistantes et câbler les deux dans vos classes de l'application.

Une autre option serait d'utiliser moqueur/stubs.

14voto

BZ. Points 188

La façon la plus simple de test de l'unité d'un objet avec accès base de données est l'aide de l'opération étendues.

Par exemple:

    [Test]
	[ExpectedException(typeof(NotFoundException))]
	public void DeleteAttendee() {

		using(TransactionScope scope = new TransactionScope()) {
			Attendee anAttendee = Attendee.Get(3);
			anAttendee.Delete();
			anAttendee.Save();

			//Try reloading. Instance should have been deleted.
			Attendee deletedAttendee = Attendee.Get(3);
		}
	}

Cela permettra de revenir à l'état de la base de données, un peu comme une annulation de la transaction, de sorte que vous pouvez exécuter le test autant de fois que vous le souhaitez sans aucun effets secondaires. Nous avons utilisé cette approche avec succès dans les grands projets. Notre build prend un peu de temps à s'exécuter (15 minutes), mais il n'est pas horrible pour avoir 1800 tests unitaires. Aussi, si le temps de construction est un problème, vous pouvez modifier le processus de construction de disposer de plusieurs versions, l'une pour la construction de la src, un autre qui se déclenche par la suite que les poignées de tests unitaires, analyse de code, l'emballage, etc...

12voto

Alan Points 7273

Je peux peut-être vous donner un avant-goût de notre expérience lorsque nous avons commencé à regarder les tests unitaires notre intermédiaire des processus qui inclus une tonne de "logique d'entreprise" les opérations sql.

Nous avons d'abord créé une couche d'abstraction qui nous a permis de "fente" raisonnable connexion de base de données (dans notre cas, nous avons simplement pris en charge un seul ODBC-type de connexion).

Une fois cela en place, nous étions alors en mesure de faire quelque chose comme cela dans notre code, nous travaillons dans le C++, mais je suis sûr que vous voyez l'idée):

GetDatabase().ExecuteSQL( "INSERT INTO foo ( bla, bla )" )

Au terme normal de l'heure, GetDatabase() retourne un objet qui les a nourris tous nos sql (y compris les requêtes), via ODBC directement à la base de données.

Ensuite, nous avons commencé à regarder les bases de données en mémoire - le meilleur par un long chemin semble être SQLite. (http://www.sqlite.org/index.html). Il est très simple à configurer et à utiliser, et nous a permis de sous-classe et remplacer GetDatabase() pour envoyer sql à une base de données en mémoire qui a été créé et détruit pour chaque test effectué.

Nous sommes encore dans les premiers stades de cela, mais il a l'air bon jusqu'ici, cependant, nous ne devons créer toutes les tables qui sont nécessaires et de les remplir avec des données de test - cependant, nous avons réduit la charge de travail un peu ici par la création d'un ensemble générique de fonctions d'assistance qui peut faire beaucoup de tout cela pour nous.

Dans l'ensemble, il a aidé énormément avec nos TDD processus, depuis ce qui me semble tout à fait inoffensif changements pour corriger certains bugs peuvent avoir assez étrange, les répercussions sur les autres (difficile à détecter) les zones de votre système en raison de la nature même des bases de données/sql.

Évidemment, nos expériences ont centré autour d'un environnement de développement C++, mais je suis sûr que vous pourriez peut-être obtenir quelque chose de semblable de travail sous PHP/Python.

Espérons que cette aide.

11voto

Martin Klinke Points 4157

Vous devez se moquent de la base de données access si vous voulez unité tester vos classes. Après tout, vous ne voulez pas la base de données de test dans un test unitaire. Ce serait un test d’intégration.

Résumé les appels loin et puis insérez un simulacre qui retourne les données attendues. Si vos classes ne font pas plus que l’exécution de requêtes, il même vaut peut-être pas tester, mais...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X