Je suggérerais de se moquer de vos appels à la base de données. Les simulacres sont essentiellement des objets qui ressemblent à l'objet sur lequel vous essayez d'appeler une méthode, dans le sens où ils ont les mêmes propriétés, méthodes, etc. disponibles pour l'appelant. Mais au lieu d'effectuer toute action pour laquelle ils sont programmés lorsqu'une méthode particulière est appelée, elle l'ignore complètement et renvoie simplement un résultat. Ce résultat est généralement défini par vous à l'avance.
Afin de configurer vos objets pour la moquerie, vous devez probablement utiliser une sorte d'inversion du modèle d'injection de contrôle / dépendance, comme dans le pseudo-code suivant:
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 test unitaire, vous créez une maquette de FooDataProvider, qui vous permet d'appeler la méthode GetAllFoos sans avoir à accéder à 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)
}
}
Un scénario moqueur courant, en un mot. Bien sûr, vous souhaiterez probablement tester également vos appels à la base de données réels, pour lesquels vous devrez accéder à la base de données.
Idéalement, vos objets devraient être ignorants persistants. Par exemple, vous devriez avoir une "couche d'accès aux données", à laquelle vous feriez des requêtes, qui renverrait des objets. De cette façon, vous pouvez laisser cette partie en dehors de vos tests unitaires ou les tester de manière isolée.
Si vos objets sont étroitement couplés à votre couche de données, il est difficile d'effectuer des tests unitaires appropriés. la première partie du test unitaire, est "unité". Toutes les unités doivent pouvoir être testées isolément.
Dans mes projets c #, j'utilise NHibernate avec une couche de données complètement séparée. Mes objets vivent dans le modèle de domaine principal et sont accessibles à partir de ma couche d'application. La couche d'application communique à la fois à la couche de données et à la couche de modèle de domaine.
La couche application est également parfois appelée «couche métier».
Si vous utilisez PHP, créez un ensemble spécifique de classes pour UNIQUEMENT accès aux données. Assurez-vous que vos objets n'ont aucune idée de la façon dont ils sont persistants et connectez les deux dans vos classes d'application.
Une autre option serait d'utiliser mocking / stubs.
la source
Le moyen le plus simple de tester un objet avec un accès à la base de données consiste à utiliser des étendues de transaction.
Par exemple:
Cela rétablira l'état de la base de données, essentiellement comme une annulation de transaction afin que vous puissiez exécuter le test autant de fois que vous le souhaitez sans aucun effet secondaire. Nous avons utilisé cette approche avec succès dans de grands projets. Notre build prend un peu de temps à s'exécuter (15 minutes), mais ce n'est pas horrible d'avoir 1800 tests unitaires. De plus, si le temps de construction est un problème, vous pouvez modifier le processus de construction pour avoir plusieurs versions, une pour la construction de src, une autre qui se déclenche ensuite qui gère les tests unitaires, l'analyse de code, l'empaquetage, etc.
la source
Je peux peut-être vous donner un avant-goût de notre expérience lorsque nous avons commencé à examiner les tests unitaires de notre processus de niveau intermédiaire, qui comprenait une tonne d'opérations SQL de «logique métier».
Nous avons d'abord créé une couche d'abstraction qui nous permettait de «insérer» n'importe quelle connexion de base de données raisonnable (dans notre cas, nous avons simplement pris en charge une seule connexion de type ODBC).
Une fois que cela a été mis en place, nous avons pu faire quelque chose comme ça dans notre code (nous travaillons en C ++, mais je suis sûr que vous avez l'idée):
GetDatabase (). ExecuteSQL ("INSERT INTO foo (blah, blah)")
Au moment de l'exécution normal, GetDatabase () renverrait un objet qui alimentait tous nos sql (y compris les requêtes), via ODBC directement dans la base de données.
Nous avons ensuite commencé à examiner les bases de données en mémoire - la meilleure de loin semble être SQLite. ( http://www.sqlite.org/index.html ). C'est remarquablement simple à configurer et à utiliser, et nous a permis de sous-classer et de surcharger GetDatabase () pour transférer SQL vers une base de données en mémoire qui a été créée et détruite pour chaque test effectué.
Nous en sommes encore aux premiers stades, mais cela semble bon jusqu'à présent, mais nous devons nous assurer de créer toutes les tables nécessaires et de les remplir avec des données de test - cependant nous avons quelque peu réduit la charge de travail ici en créant un ensemble générique de fonctions d'assistance qui peuvent faire beaucoup de tout cela pour nous.
Dans l'ensemble, cela a énormément aidé avec notre processus TDD, car apporter ce qui semble être des modifications assez inoffensives pour corriger certains bogues peut avoir des effets assez étranges sur d'autres zones (difficiles à détecter) de votre système - en raison de la nature même de sql / bases de données.
Évidemment, nos expériences se sont centrées sur un environnement de développement C ++, mais je suis sûr que vous pourriez peut-être obtenir quelque chose de similaire fonctionnant sous PHP / Python.
J'espère que cela t'aides.
la source
Vous devez vous moquer de l'accès à la base de données si vous souhaitez tester vos classes unitaire. Après tout, vous ne voulez pas tester la base de données dans un test unitaire. Ce serait un test d'intégration.
Abstenez les appels, puis insérez un simulacre qui renvoie simplement les données attendues. Si vos classes ne font pas plus qu'exécuter des requêtes, cela ne vaut même pas la peine de les tester, cependant ...
la source
Le livre xUnit Test Patterns décrit quelques façons de gérer le code de test unitaire qui atteint une base de données. Je suis d'accord avec les autres personnes qui disent que tu ne veux pas faire ça parce que c'est lent, mais tu dois le faire un jour, l'OMI. Se moquer de la connexion à la base de données pour tester des éléments de niveau supérieur est une bonne idée, mais consultez ce livre pour des suggestions sur ce que vous pouvez faire pour interagir avec la base de données réelle.
la source
Options dont vous disposez:
Injectez la base de données. (Exemple en pseudo-Java, mais s'applique à tous les langages OO)
maintenant, en production, vous utilisez une base de données normale et pour tous les tests, vous injectez simplement la base de données fictive que vous pouvez créer ad hoc.User
au lieu d'un tuple{name: "marcin", password: "blah"}
), écrivez tous vos tests avec des objets réels construits ad hoc et écrivez un gros test qui dépend d'une base de données qui assure cette conversion fonctionne bien.Bien sûr, ces approches ne sont pas mutuellement exclusives et vous pouvez les mélanger et les assortir selon vos besoins.
la source
Le test unitaire de l'accès à votre base de données est assez facile si votre projet a une cohésion élevée et un couplage lâche. De cette façon, vous pouvez tester uniquement les choses que chaque classe particulière fait sans avoir à tout tester en même temps.
Par exemple, si vous testez l'unité de votre classe d'interface utilisateur, les tests que vous écrivez doivent uniquement essayer de vérifier que la logique à l'intérieur de l'interface utilisateur a fonctionné comme prévu, pas la logique métier ou l'action de base de données derrière cette fonction.
Si vous souhaitez effectuer un test unitaire de l'accès réel à la base de données, vous vous retrouverez en fait avec plus d'un test d'intégration, car vous dépendez de la pile réseau et de votre serveur de base de données, mais vous pouvez vérifier que votre code SQL fait ce que vous lui avez demandé. faire.
Le pouvoir caché des tests unitaires pour moi est que cela me force à concevoir mes applications d'une bien meilleure manière que je ne le pourrais sans eux. C'est parce que cela m'a vraiment aidé à rompre avec la mentalité «cette fonction devrait tout faire».
Désolé, je n'ai pas d'exemple de code spécifique pour PHP / Python, mais si vous voulez voir un exemple .NET, j'ai un article qui décrit une technique que j'ai utilisée pour faire ces mêmes tests.
la source
J'essaie généralement de fractionner mes tests entre le test des objets (et l'ORM, le cas échéant) et le test de la base de données. Je teste le côté objet des choses en se moquant des appels d'accès aux données alors que je teste le côté db des choses en testant les interactions des objets avec la base de données qui est, selon mon expérience, généralement assez limitée.
J'étais frustré par l'écriture de tests unitaires jusqu'à ce que je commence à me moquer de la partie d'accès aux données, donc je n'avais pas à créer une base de données de test ou à générer des données de test à la volée. En vous moquant des données, vous pouvez tout générer au moment de l'exécution et vous assurer que vos objets fonctionnent correctement avec des entrées connues.
la source
Je n'ai jamais fait cela en PHP et je n'ai jamais utilisé Python, mais ce que vous voulez faire est de simuler les appels à la base de données. Pour ce faire, vous pouvez implémenter une certaine IoC, qu'il s'agisse d'un outil tiers ou que vous le gériez vous-même, puis vous pouvez implémenter une version simulée de l'appelant de la base de données qui vous permettra de contrôler le résultat de ce faux appel.
Une forme simple d'IoC peut être réalisée simplement en codant sur Interfaces. Cela nécessite une sorte d'orientation d'objet dans votre code, donc cela peut ne pas s'appliquer à ce que vous faites (je dis que puisque tout ce que je dois continuer, c'est votre mention de PHP et Python)
J'espère que cela vous sera utile, si rien d'autre vous avez des termes à rechercher maintenant.
la source
Je suis d'accord avec le premier accès post-base de données devrait être dépouillé dans une couche DAO qui implémente une interface. Ensuite, vous pouvez tester votre logique par rapport à une implémentation de stub de la couche DAO.
la source
Vous pouvez utiliser des cadres de simulation pour extraire le moteur de base de données. Je ne sais pas si PHP / Python en a mais pour les langages typés (C #, Java etc.) il y a beaucoup de choix
Cela dépend également de la façon dont vous avez conçu ce code d'accès à la base de données, car certaines conceptions sont plus faciles à tester unitaire que d'autres comme celles mentionnées précédemment.
la source
La configuration des données de test pour les tests unitaires peut être un défi.
En ce qui concerne Java, si vous utilisez les API Spring pour les tests unitaires, vous pouvez contrôler les transactions au niveau de l'unité. En d'autres termes, vous pouvez exécuter des tests unitaires qui impliquent des mises à jour / inserts / suppressions de base de données et annuler les modifications. À la fin de l'exécution, vous laissez tout dans la base de données tel qu'il était avant de commencer l'exécution. Pour moi, c'est aussi bon que possible.
la source