web-dev-qa-db-fra.com

Comment simuler une base de données pour les tests (Java)?

Je programme en Java et mes applications utilisent beaucoup la base de données. Par conséquent, il est important pour moi de pouvoir tester facilement mon utilisation de base de données.
Quels sont les tests de base de données? Pour moi, ils devraient fournir deux exigences simples: 

  1. Vérifiez la syntaxe SQL.
  2. Plus important encore, vérifiez que les données sont sélectionnées/mises à jour/insérées correctement, en fonction d'une situation donnée. 

Eh bien, il me semble que tout ce dont j'ai besoin est un DB.
Mais en fait, je ne préfère pas, car il y a peu de difficultés à utiliser un DB pour un test: 

  • "Procurez-vous une base de test, à quel point cela pourrait-il être difficile?" - Eh bien, sur mon lieu de travail, disposer d'une base de données de tests personnelle est quasiment impossible. Vous devez utiliser une base de données "publique" accessible à tous.
  • "Ces tests ne sont certainement pas rapides ..." - Les tests DB ont tendance à être plus lents que les tests habituels. Ce n'est vraiment pas idéal d'avoir des tests lents.
  • "Ce programme devrait gérer tous les cas!" - Il devient quelque peu agaçant et même impossible d'essayer de simuler chaque cas dans une base de données. Pour chaque cas, un certain nombre de requêtes d'insertion/mise à jour doivent être effectuées, ce qui est gênant et prend du temps.
  • "Attendez une seconde, comment savez-vous qu'il y a 542 lignes dans cette table?" - L'un des principes de base des tests est de pouvoir tester les fonctionnalités de manière différente de celle de votre code testé. Lorsque vous utilisez une base de données, il y a généralement une façon de faire quelque chose, le test est donc exactement le même que le code principal.

Donc, vous pouvez comprendre que je n'aime pas les bases de données quand il s'agit de tests (bien sûr, je vais devoir y arriver dans un moment, mais je préférerais y arriver plus tard, après avoir trouvé la plupart des bugs reste des méthodes d’essai). Mais qu'est-ce que je cherche? 

Je cherche un moyen de simuler une base de données, une base de données fictive, en utilisant le système de fichiers ou simplement la mémoire virtuelle. Je pensais qu'il existait peut-être un outil/package Java permettant de construire simplement (à l'aide d'une interface de code) une simulation de base de données par test, avec des tables et des lignes simulées, avec une vérification SQL et une interface de code permettant de surveiller son statut (plutôt qu'avec SQL ). 

Connaissez-vous ce type d’outil?


Edit: / Merci pour les réponses! Même si je demandais un outil, vous m'avez également fourni quelques astuces concernant le problème :) Il me faudra un peu de temps pour vérifier vos offres, je ne peux donc pas vous dire si vos réponses ont été satisfaisantes.

Quoi qu'il en soit, voici une meilleure vue de ce que je recherche: imaginons une classe nommée DBMonitor, dont l'une des fonctionnalités consiste à rechercher le nombre de lignes dans une table. Voici un code imaginaire sur la façon dont je voudrais tester cette fonctionnalité à l'aide de JUnit:

public class TestDBMonitor extends TestCase {

    @Override
    public void setUp() throws Exception {

       MockConnection connection = new MockConnection();

       this.tableName = "table1";
       MockTable table = new MockTable(tableName);

       String columnName = "column1";
       ColumnType columnType = ColumnType.NUMBER;
       int columnSize = 50;
       MockColumn column = new MockColumn(columnName, columnType, columnSize);
       table.addColumn(column);

       for (int i = 0; i < 20; i++) {
           HashMap<MockColumn, Object> fields = new HashMap<MockColumn, Object>();
           fields.put(column, i);
           table.addRow(fields);
       }

       this.connection = connection;
    }

    @Test
    public void testGatherStatistics() throws Exception {

       DBMonitor monitor = new DBMonitor(connection);
       monitor.gatherStatistics();
       assertEquals(((MockConnection) connection).getNumberOfRows(tableName),
                    monitor.getNumberOfRows(tableName));
    }

    String tableName;
    Connection connection;
}

J'espère que ce code est suffisamment clair pour comprendre mon idée (excusez-moi des erreurs de syntaxe, je tapais manuellement sans ma chère Eclipse: P). 

En passant, j'utilise partiellement ORM, et mes requêtes SQL brutes sont assez simples et ne devraient pas différer d'une plate-forme à l'autre. 

59
Eyal Roth

nouvelle réponse à l'ancienne question (mais les choses ont un peu avancé):

Comment simuler une base de données pour les tests (Java)?

vous ne le simulez pas. vous vous moquez de vos dépôts et vous ne les testez pas ou vous utilisez la même base de données dans vos tests et vous testez vos fichiers SQL. Tous les dbs en mémoire ne sont pas entièrement compatibles, ils ne vous donneront donc pas une couverture et une fiabilité complètes. et ne jamais essayer de simuler/simuler des objets de base de données tels que connexion, jeu de résultats, etc. il ne vous donne aucune valeur et constitue un cauchemar à développer et à maintenir.

avoir une base de test personnelle est assez impossible. Vous devez utiliser une base de données "publique" accessible à tous.

malheureusement, beaucoup de sociétés utilisent encore ce modèle, mais maintenant nous avons docker et il existe des images pour presque chaque base de données. Les produits commerciaux ont des limitations (par exemple, quelques gb de données) qui ne sont pas importantes pour les tests. De plus, vous avez besoin que votre schéma et votre structure soient créés sur cette base de données locale.

"Ces tests ne sont certainement pas rapides ..." - Les tests DB ont tendance à être plus lents que les tests habituels. Ce n'est vraiment pas idéal d'avoir des tests lents.

oui, les tests de base de données sont plus lents mais ils ne le sont pas aussi. J'ai fait quelques mesures simples et un test typique prenait 5-50ms. ce qui prend du temps, c'est le démarrage de l'application. il y a beaucoup de manières d'accélérer ceci:

  • les premiers cadres DI (comme Spring) offrent un moyen de gérer seulement une partie de votre application. si vous écrivez votre application avec une bonne séparation de la logique de la base de données et de la logique non liée à la base de données, vous ne pourrez démarrer que la partie base de données dans le test
  • chaque base de données possède de nombreuses options de réglage qui la rendent moins durable et beaucoup plus rapide. c'est parfait pour les tests. exemple postgres
  • vous pouvez aussi mettre toute la base de données dans tmpfs

  • une autre stratégie utile consiste à avoir des groupes de tests et à garder les tests de base de données désactivés par défaut (s'ils ralentissent réellement votre construction). Ainsi, si quelqu'un travaille actuellement sur la base de données, il doit passer un indicateur supplémentaire dans la ligne de commande ou utiliser IDE (les groupes de test et les sélecteurs de test personnalisés sont parfaits pour cela).

Pour chaque cas, un certain nombre de requêtes d'insertion/mise à jour doivent être effectuées, ce qui est agaçant et prend du temps.

La partie "prend du temps" a été discutée ci-dessus. est-ce agaçant? J'ai vu deux manières:

  • préparez un jeu de données pour tous vos tests. alors vous devez le maintenir et raisonner à ce sujet. généralement, il est séparé du code. il a kilo-octets ou mégaoctets. c'est trop grand pour voir sur un écran, pour comprendre et pour raisonner. il introduit un couplage entre les tests. car lorsque vous avez besoin de plus de lignes pour le test A, votre count(*) dans le test B échoue. il ne fait que croître car même lorsque vous supprimez certains tests, vous ne savez pas quelles lignes ont été utilisées uniquement par ce test
  • chaque test prépare ses données. Ainsi, chaque test est complètement indépendant, lisible et facile à raisonner. est-ce agaçant? imo, pas du tout! il vous permet d'écrire de nouveaux tests très rapidement et vous épargne beaucoup de travail à l'avenir

comment savez-vous qu'il y a 542 lignes dans cette table? "- L'un des principes fondamentaux du test est de pouvoir tester la fonctionnalité différemment de celle de votre code testé.

euh ... pas vraiment. le principe consiste à vérifier si votre logiciel génère le résultat souhaité en réponse à une entrée spécifique. Par conséquent, si vous appelez dao.insert 542 fois et que votre dao.count renvoie 542, cela signifie que votre logiciel fonctionne comme spécifié. si vous le souhaitez, vous pouvez appeler commit/drop cache entre les deux. Bien sûr, vous souhaitez parfois tester votre implémentation au lieu du contrat, puis vous vérifiez si votre DAO a modifié l'état de la base de données. mais vous testez toujours SQL A avec SQL B (insert vs select, séquence next_val vs valeur renvoyée, etc.). oui, vous aurez toujours le problème 'qui va tester mes tests', et la réponse est: personne, alors soyez simples!

d'autres outils pouvant vous aider:

  1. testcontainers vous aidera à fournir real db.

  2. dbunit - vous aidera à nettoyer les données entre les tests 

    les inconvénients: 

    • il faut beaucoup de travail pour créer et gérer des schémas et des données. surtout lorsque votre projet est dans une phase de développement intensive.
    • c'est une autre couche d'abstraction, donc si vous voulez soudainement utiliser une fonctionnalité de base de données non prise en charge par cet outil, il peut être difficile de la tester.
  3. testegration - a pour objectif de vous fournir un cycle de vie complet, prêt à l'emploi et extensible (divulgation: je suis un créateur). 

    les inconvénients: 

    • gratuit seulement pour les petits projets
    • très jeune projet
  4. flyway ou liquibase - outils de migration de base de données. ils vous aident à créer facilement un schéma et toutes les structures de votre base de données locale pour les tests.

12
piotrek

Java est livré avec Java DB .

Cela dit, je vous déconseille d'utiliser un type de base de données différent de celui utilisé en production, sauf si vous passez par une couche ORM. Sinon, votre code SQL pourrait ne pas être aussi multi-plateforme que vous le pensez.

Consultez également DbUnit

40
ykaganovich

J'ai utilisé Hypersonic à cette fin. Fondamentalement, il s’agit d’un fichier JAR (une base de données Java pure en mémoire) que vous pouvez exécuter dans sa propre JVM ou dans votre propre JVM et, lors de son exécution, vous disposez d’une base de données. Ensuite, vous l'arrêtez et votre base de données s'en va. Je l'ai utilisée - jusqu'à présent - comme base de données purement en mémoire. C'est très simple de démarrer et d'arrêter via Ant lorsque vous exécutez des tests unitaires.

10
Eddie

Il existe de nombreux points de vue sur la façon de tester des points d'intégration tels que la connexion à la base de données via SQL. Voici mes règles personnelles qui ont bien fonctionné pour moi:

1) Séparez la logique d’accès à la base de données et ses fonctions de la logique commerciale générale et cachez-la derrière une interface .. Raison: pour tester la grande majorité de la logique du système, il est préférable d’utiliser un modèle factice/stub à la place de la base de données réelle en tant que plus simple . Raison 2: il est considérablement plus rapide

2) Traiter les tests de la base de données comme des tests d’intégration séparés du corps principal des tests unitaires et devant être exécutés sur une base de données de configuration. Reason: rapidité et qualité des tests.

3) Chaque développeur aura besoin de sa propre base de données distincte. Ils auront besoin d'un moyen automatisé pour mettre à jour sa structure en fonction des modifications apportées par leurs coéquipiers et pour introduire des données. Voir points 4 et 5.

4) Utilisez un outil tel que http://www.liquibase.org pour gérer les mises à niveau de la structure de votre base de données ..__ Reason: vous permet de modifier la structure existante et d’avancer dans les versions.

5) Utilisez un outil tel que http://dbunit.sourceforge.net/ pour gérer les données. Configurez des fichiers de scénario (XML ou XLS) pour des scénarios de test particuliers et des données de base et définissez uniquement ce qui est nécessaire pour chaque scénario de test .. Raison: bien mieux que d’insérer et de supprimer manuellement des données Raison 2: Plus facile pour. testeurs pour comprendre comment ajuster les scénarios Raison 3: Il est plus rapide d’exécuter cette

6) Vous avez besoin de tests fonctionnels comportant également des données de scénario similaires à DBUnit, mais il s’agit de jeux de données beaucoup plus volumineux qui exécutent l’ensemble du système. Ceci termine l’étape consistant à combiner les connaissances selon lesquelles A. Les tests unitaires sont exécutés et, par conséquent, la logique est bonne B) que les tests d’intégration à l’exécution de la base de données et que le code SQL est correct dans son ensemble travaille ensemble comme une pile de haut en bas "

Cette combinaison m’a bien servi jusqu’à présent pour obtenir une qualité élevée de tests et de produits, ainsi que pour maintenir la vitesse de développement des tests unitaires et l’agilité face au changement.

10
Paul Keeble

"Procurez-vous une base de test, à quel point cela pourrait-il être difficile?" - Eh bien, sur mon lieu de travail, disposer d'une base de données de tests personnelle est assez impossible. Vous devez utiliser une base de données "publique" accessible à tous.

On dirait que vous avez des problèmes culturels au travail qui vous empêchent de faire votre travail au maximum de vos capacités et de tirer le meilleur parti de votre produit. Vous voudrez peut-être faire quelque chose à ce sujet.

D'autre part, si votre schéma de base de données est sous contrôle de version, vous pouvez toujours avoir une version de test qui crée une base de données à partir du schéma, la remplit avec des données de test, exécute vos tests, rassemble les résultats, puis supprime la base de données. Il n'existerait que pendant la durée des tests. Il peut s'agir d'une nouvelle base de données sur une installation existante si le matériel pose un problème. Ceci est similaire à ce que nous faisons où je travaille.

6
banjollity

Si vous utilisez Oracle au travail, vous pouvez utiliser la fonctionnalité Point de restauration dans la base de données Flashback pour que la base de données revienne à une date antérieure à vos tests. Cela effacera toutes les modifications que vous avez personnellement apportées à la base de données.

Voir:

https://docs.Oracle.com/cd/E11882_01/backup.112/e10642/flashdb.htm#BRADV71000

Si vous avez besoin d'une base de données de test à utiliser avec la production/le travail Oracle, recherchez la base de données XE, Express Edition auprès d'Oracle. Ceci est gratuit pour un usage personnel, avec une limite de base de données inférieure à 2 Go.

4
Martlark

Nous avons récemment opté pour JavaDB ou Derby pour implémenter cela. Derby 10.5.1.1 implémente maintenant une représentation en mémoire afin qu’il soit très rapide, il n’a pas besoin d’aller sur disque: Derby In Memory Primer

Nous concevons notre application pour s'exécuter sur Oracle, PostgreSQL et Derby, de sorte que nous n'allions pas trop loin sur une plate-forme quelconque avant de découvrir qu'une base de données prend en charge une fonctionnalité contrairement à d'autres.

3
Blair Zajac

Je pense que mon framework Acolyte peut être utilisé pour une telle maquette de base de données: https://github.com/cchantep/acolyte .

Il permet d’exécuter Java (à des fins de test) avec les connexions dont vous avez la gestion des requêtes/mises à jour: renvoyer les résultats appropriés, le nombre de mises à jour ou l’avertissement en fonction des cas d’exécution.

1
cchantep

Nous créons actuellement un environnement de test de base de données au travail. Nous estimons que nous devons utiliser un système de gestion de base de données real avec données simulées. Un problème avec un SGBD simulé est que SQL n'est jamais vraiment totalement gélifié comme standard, de sorte qu'un environnement de test artificiel devrait supporter fidèlement le dialecte de notre base de données de production. Un autre problème est que nous utilisons beaucoup les contraintes de valeur de colonne, de clé étrangère et de contraintes uniques, et qu’un outil artificiel ne les implémenterait probablement pas, nos tests unitaires pourraient réussir, mais nos tests système échoueraient au premier abord. contraintes. Si les tests prennent trop de temps, cela indique une erreur d'implémentation et nous réglons nos requêtes (les ensembles de données de test sont généralement minuscules par rapport à la production).

Nous avons installé un véritable SGBD sur chaque poste de développement et sur notre serveur d'intégration et de test en continu (nous utilisons Hudson). Je ne sais pas quelles sont vos restrictions de politique de travail, mais il est assez facile d'installer et d'utiliser PostgreSQL, MySQL et Oracle XE. Tous ces éléments sont gratuits pour le développement (même Oracle XE), il n’ya donc aucune raison rationnelle d’interdire leur utilisation. 

La question clé est de savoir comment garantir que vos tests commencent toujours avec la base de données dans un état cohérent. Si les tests étaient tous en lecture seule, pas de problème. Si vous pouviez concevoir des tests de mutations pour qu'ils s'exécutent toujours dans des transactions qui ne sont jamais validées, aucun problème. Mais généralement, vous devez vous soucier de l’inversion des mises à jour. Pour ce faire, vous pouvez exporter l'état initial dans un fichier, puis l'importer de nouveau après le test (les commandes exp et imp Shell d'Oracle le font). Ou vous pouvez utiliser un point de contrôle/restauration. Mais une méthode plus élégante consiste à utiliser un outil tel que dbunit , qui fonctionne bien pour nous.

Le principal avantage de cette solution est que nous détectons beaucoup plus de bogues dès le départ, ce qui les rend beaucoup plus faciles à corriger et que les tests réels de notre système ne sont pas bloqués, tandis que les développeurs tentent fébrilement de résoudre les problèmes. Cela signifie que nous produisons un meilleur code plus rapidement et avec moins d'effort.

1
Jim Ferrans

Essayez d'utiliser derby . C'est facile et portable. Avec Hibernate, votre application devient flexible. Testez sur le derby, la production sur tout ce que vous aimez et faites confiance.

1
Artic

Je suis d'accord avec banjollity. La configuration d’environnements de développement et de test isolés doit être une priorité absolue. Chaque système de base de données que j'ai utilisé est open source ou est fourni avec une édition pour développeurs gratuite que vous pouvez installer sur votre poste de travail local. Cela vous permet de développer avec le même dialecte de base de données que la production, vous donne un accès administrateur complet aux bases de développement et est plus rapide que d’utiliser un serveur distant. 

1
Nat

Vous pouvez utiliser HSQLDB pour les tests de la base de données en mémoire. Démarrer la base de données en mémoire et y exécuter des tests est assez simple.
http://hsqldb.org/

1
Pratik Singhal

jOOQ est un outil qui, outre le fait de proposer une abstraction SQL, intègre également de petits outils, tels qu'un SPI, qui permettent de se moquer de JDBC dans son ensemble. _ { Cela peut fonctionner de deux manières, comme indiqué dans ce billet de blog } _:

En mettant en œuvre le MockDataProvider SPI:

// context contains the SQL string and bind variables, etc.
MockDataProvider provider = context -> {

    // This defines the update counts, result sets, etc.
    // depending on the context above.
    return new MockResult[] { ... }
};

Dans l'implémentation ci-dessus, vous pouvez intercepter par programmation chaque instruction SQL et lui renvoyer un résultat, même de manière dynamique, en "analysant" la chaîne SQL afin d'extraire des informations de prédicats/table, etc.

En utilisant le plus simple (mais moins puissant) MockFileDatabase

... qui a le format suivant (un ensemble de paires instruction/résultat):

select first_name, last_name from actor;
> first_name last_name
> ---------- ---------
> GINA       DEGENERES
> WALTER     TORN     
> MARY       KEITEL   
@ rows: 3

Le fichier ci-dessus peut alors être lu et consommé comme suit:

import static Java.lang.System.out;
import Java.sql.*;
import org.jooq.tools.jdbc.*;

public class Mocking {
    public static void main(String[] args) throws Exception {
        MockDataProvider db = new MockFileDatabase(
            Mocking.class.getResourceAsStream("/mocking.txt");

        try (Connection c = new MockConnection(db));
            Statement s = c.createStatement()) {

            out.println("Actors:");
            out.println("-------");
            try (ResultSet rs = s.executeQuery(
                "select first_name, last_name from actor")) {
                while (rs.next())
                    out.println(rs.getString(1) 
                        + " " + rs.getString(2));
            }
        }
    }
}

Remarquez comment nous utilisons l'API JDBC directement, sans nous connecter à aucune base de données.

Remarquez, je travaille pour le vendeur de jOOQ alors cette réponse est biaisée.

Attention, à un moment donné, vous implémentez une base de données complète

Ce qui précède fonctionne pour des cas simples. Mais attention, vous allez éventuellement implémenter une base de données complète. Tu veux:

  1. Vérifiez la syntaxe SQL.

OK, en moquant la base de données comme indiqué ci-dessus, vous pouvez "vérifier" la syntaxe, car chaque syntaxe que vous n'avez pas prévue dans la version exact indiquée ci-dessus sera rejetée par une telle approche moqueuse.

Vous pouvez implémenter un analyseur syntaxique qui analyse SQL ( ou, encore, utiliser jOOQ ), puis transformer l'instruction SQL en un document que vous pouvez plus facilement reconnaître et produire pour obtenir un résultat. Mais en fin de compte, cela signifie simplement mettre en œuvre une base de données complète.

  1. Plus important encore, vérifiez que les données sont sélectionnées/mises à jour/insérées correctement, en fonction d'une situation donnée.

Cela rend les choses encore plus difficiles. Si vous exécutez une insertion puis une mise à jour, le résultat est évidemment différent de la mise à jour en premier, puis à l'insertion, car la mise à jour peut ou non affecter la ligne insérée.

Comment vous assurez-vous que cela se produit lorsque vous "moquez" d'une base de données? Vous avez besoin d'une machine à états qui se souvient de l'état de chaque table "fausse". En d'autres termes, vous allez implémenter une base de données.

Se moquer ne vous mènera que loin

Comme piotrek mentionné, les moqueries ne vous mèneront que si loin. C'est utile dans les cas simples, lorsque vous n'avez besoin d'intercepter que quelques requêtes très connues. Il est impossible si vous voulez simuler la base de données pour un système entier. Dans ce cas, utilisez une base de données réelle, idéalement le même produit que celui que vous utilisez en production.

0
Lukas Eder

Pour commencer, utilisez-vous une couche ORM pour l’accès à la base de données?
Si non: alors ce que vous pensez ne serait d'aucune utilité. À quoi sert de tester quand vous n'êtes pas sûr que SQL que vous lancez fonctionnera avec votre base de données en production car dans les cas de test, vous utilisez autre chose
Si oui: vous pouvez alors regarder les différentes options soulignées.

0
Khangharoth