23 votes

Comment créer des tests unitaires contre une base de données qui n'est pas en mémoire, comme MySQL, dans le cadre de Play, avec une réinitialisation à un état connu ?

Je veux créer des tests unitaires qui couvrent le code qui utilise une base de données relationnelle dans le Play framework 2.1.0. Il existe de nombreuses possibilités pour cela et toutes posent des problèmes :

Test sur la base de données H2 en mémoire

La documentation du framework Play propose d'exécuter les tests unitaires sur la base de données en mémoire H2, même si la base de données principale utilisée pour le développement et la production utilise un autre logiciel (par exemple, MySQL) :

app = Helpers.fakeApplication(Helpers.inMemoryDatabase());

Mon application n'utilise pas de fonctionnalités compliquées du SGBDR telles que les procédures stockées et la plupart des cas d'accès à la base de données sont des appels ebean, elle doit donc être compatible à la fois avec MySQL et H2.

Cependant, les instructions de création de table dans les évolutions utilisent des fonctionnalités spécifiques à MySQL, telles que la spécification de l'option ENGINE = InnoDB , DEFAULT CHARACTER SET = utf8 etc. J'ai peur que si je supprime ces parties exclusives de l'entreprise, je n'aie pas le choix. CREATE TABLE Pour tester et développer l'application, la configuration principale de MySQL doit donc être modifiée.

Quelqu'un a-t-il utilisé cette approche (rendre les évolutions compatibles à la fois avec MySQL et H2) ?

D'autres idées sur la façon dont cela peut être traité :

  • Évolutions séparées pour MySQL et H2 (pas une bonne idée)
  • Un moyen de faire en sorte que H2 ignore les éléments supplémentaires de MySQL dans create table (Le mode de compatibilité de MySQL ne fonctionne pas, il se plaint toujours même sur default character set ). Je ne sais pas comment.

Test sur le même pilote de base de données que la base de données principale

Le seul avantage de la base de données H2 en mémoire est sa rapidité, et il est préférable de tester sur le même pilote de base de données que la base de données de développement/production, car il est plus proche de l'environnement réel.

Comment le faire correctement dans le cadre de Play ?

Essayé :

Map<String, String> settings = new HashMap<String, String>();
settings.put("db.default.url", "jdbc:mysql://localhost/sometestdatabase");
settings.put("db.default.jndiName", "DefaultDS");
app = Helpers.fakeApplication(settings);

Il semble que les évolutions fonctionnent ici, mais comment nettoyer au mieux la base de données avant chaque test ? En créant un code personnalisé qui tronque chaque table ? Si les tables sont supprimées, les évolutions seront-elles exécutées à nouveau avant le prochain test, ou seront-elles appliquées une fois par test ? play test commande ? Ou une fois par Helpers.fakeApplication() invocation ?

Quelles sont les meilleures pratiques en la matière ? Entendu parler de dbunit est-il possible de l'intégrer sans trop de difficultés et de bizarreries ?

8voto

mguillermin Points 2259

Tout d'abord, je vous recommande d'utiliser le même SGBDR pour les tests et la production, car cela pourrait éviter certains bogues difficiles à trouver.

En ce qui concerne la nécessité de nettoyer votre base de données entre chaque test, vous pouvez utiliser Ebean DdlGenerator pour générer des scripts afin de créer une base de données propre et l'outil de JUnit @Before pour exécuter automatiquement ces scripts avant chaque test.

Utilisation de la DdlGenerator peut être fait comme ceci :

    EbeanServer server = Ebean.getServer(serverName);
    ServerConfig config = new ServerConfig();
    DdlGenerator ddl = new DdlGenerator((SpiEbeanServer) server, new MySqlPlatform(), config);

Ce code peut être placé dans une classe de base dont vous pourriez faire hériter vos tests (ou dans une classe de base personnalisée). Runner que vous pouvez utiliser avec le @RunWith annotation).

Il vous permettra également d'automatiser facilement la FakeApplication création, en évitant un code passe-partout.

Quelques liens qui peuvent être utiles :

5voto

kolen Points 712

J'ai utilisé le même moteur de base de données que la base de données principale et dbunit pour le nettoyage avant chaque test.

public class SomeTest {
    // ...

    @Before
    public void startApp() throws Exception {
        // Set up connection to test database, different from main database. Config better should be used instead of hard-coding.
        Map<String, String> settings = new HashMap<String, String>();
        settings.put("db.default.url", "jdbc:mysql://localhost/somedatabase?characterEncoding=UTF-8&useOldAliasMetadataBehavior=true");
        settings.put("db.default.user", "root");
        settings.put("db.default.password", "root");
        settings.put("db.default.jndiName", "DefaultDS"); // make connection available to dbunit through JNDI
        app = Helpers.fakeApplication(settings);
        Helpers.start(app);

        databaseTester = new JndiDatabaseTester("DefaultDS");

        IDataSet initialDataSet = new FlatXmlDataSetBuilder().build(play.Play.application()
                .resourceAsStream("/resources/dataset.xml"));
        databaseTester.setDataSet(initialDataSet);
        databaseTester.onSetup();
    }

    @After
    public void stopApp() throws Exception {
        databaseTester.onTearDown();
        Helpers.stop(app);
    }
}

Mon dataset.xml contiennent juste les noms des tables pour indiquer à dbunit de vider ces tables avant chaque test. Il peut aussi contenir des fixtures.

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
  <name_of_my_first_table />
  <name_of_my_second_table />
</dataset>

Les évolutions s'exécutent automatiquement sur la base de données de test lorsque vous utilisez cette approche, donc si vous supprimez toutes les tables de la base de données de test, elles seront recréées.

Il est inutile d'utiliser dbunit si vous avez seulement besoin de nettoyer des tables, vous pouvez les nettoyer en émettant une requête directement ou en utilisant ebean. DdlGenerator . Mais j'utilise aussi dbunit pour comparer les données.

Je n'utilise pas Helpers.running parce qu'il faut Runnable y Runnable Les implémentations ne peuvent pas lancer d'exceptions - très gênant pour les tests. Mais si vous regardez le code de running() il appelle simplement Helpers.start() y Helpers.stop() J'appelle donc ces méthodes directement dans @Before y @After .

J'ai décidé de ne pas utiliser H2 pour les tests : oui, il fonctionne plus rapidement, mais il y a trop de différences entre lui et MySQL.

1voto

EECOLOR Points 6341

Quelqu'un a-t-il utilisé cette approche (rendre les évolutions compatibles à la fois avec MySQL et H2) ?

J'ai trouvé une réponse pour les fonctionnalités spécifiques à MySQL : Comment puis-je effectuer un test unitaire pour une base de données MySQL avec Play 2.x ?

1voto

piotryespeterno Points 11

Lorsque j'ai écrit mes tests pour ma base de données postgres, j'ai simplement créé un HashMap pour me connecter à la base de données, puis j'ai écrit des requêtes de test pour m'assurer que le nombre correct d'enregistrements existe et ainsi de suite... Voici mon code.

    @Test
public void testDataBase() {
    final HashMap<String,String> postgres = new HashMap<String, String>();
    postgres.put("db.default.driver","org.postgresql.Driver");
    postgres.put("db.default.url","jdbc:postgresql://localhost/myDataBase");
    postgres.put("db.default.user", "postgres");
    postgres.put("db.default.password", "password");

    running(fakeApplication(postgres), new Runnable() {

        @Override
        public void run() {

            //Insert Assertions Here
        }
    });
}

1voto

applicius Points 43

Vous pouvez également utiliser la simulation de la base de données si votre objectif est de valider les mappings et les fonctions de Slick|JPA|Anorm sur cette base.

Lorsqu'elle est adaptée, elle a l'avantage d'être plus conforme aux tests unitaires qu'une base de données de tests, et plus facile à gérer (pas de tâches de configuration/clôture, pas de synchronisation des tests pour éviter l'accès aux mêmes tables de tests).

Vous pouvez jeter un coup d'œil à mon cadre Acolyte ( http://github.com/cchantep/acolyte ) qui est utilisé dans les spécifications d'Anorm lui-même (par ex. https://github.com/playframework/playframework/blob/master/framework/src/anorm/src/test/scala/anorm/SqlResultSpec.scala ).

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