Comment créer des tests d'intégration gratuits évolutifs et sans effets secondaires?

14

Dans mon projet actuel, j'ai du mal à trouver une bonne solution pour créer des tests d'intégration évolutifs sans effets secondaires. Une petite clarification sur la propriété sans effet secondaire: il s'agit principalement de la base de données; il ne devrait y avoir aucun changement dans la base de données une fois les tests terminés (l'état doit être conservé). Peut-être que l'évolutivité et la préservation de l'état ne se rejoignent pas, mais je veux vraiment pousser pour une meilleure solution.

Voici un test d'intégration typique (ces tests touchent la couche de base de données):

public class OrderTests {

    List<Order> ordersToDelete = new ArrayList<Order>(); 

    public testOrderCreation() {
        Order order = new Order();
        assertTrue(order.save());
        orderToDelete.add(order);
    }

    public testOrderComparison() {
        Order order = new Order();
        Order order2 = new Order();
        assertFalse(order.isEqual(order2);
        orderToDelete.add(order);
        orderToDelete.add(order2);
    }
    // More tests

    public teardown() {
         for(Order order : ordersToDelete)
             order.delete();
    }
}

Comme on pourrait l'imaginer, cette approche donne des tests extrêmement lents. Et, lorsqu'il est appliqué à l'ensemble des tests d'intégration, il faut environ 5 secondes pour tester uniquement une petite partie du système. Je peux imaginer que ce nombre augmente lorsque la couverture est augmentée.

Quelle serait une autre approche pour écrire de tels tests? Une alternative à laquelle je peux penser est d'avoir des types de variables globales (au sein d'une classe) et toutes les méthodes de test partagent cette variable. En conséquence, seules quelques commandes sont créées et supprimées; résultant en des tests plus rapides. Cependant, je pense que cela introduit un problème plus important; les tests ne sont plus isolés et il devient de plus en plus difficile de les comprendre et de les analyser.

Il se peut simplement que les tests d'intégration ne soient pas destinés à être exécutés aussi souvent que les tests unitaires; par conséquent, de faibles performances pourraient être acceptables pour ceux-ci. Dans tous les cas, ce serait formidable de savoir si quelqu'un a trouvé des alternatives pour améliorer l'évolutivité.

Guven
la source

Réponses:

6

Envisagez d'utiliser Hypersonic ou une autre base de données en mémoire pour les tests unitaires. Non seulement les tests s'exécuteront plus rapidement, mais les effets secondaires ne sont pas pertinents. (La restauration des transactions après chaque test permet également d'exécuter de nombreux tests sur la même instance)

Cela vous obligera également à créer des maquettes de données, ce qui est une bonne chose OMI, car cela signifie que quelque chose qui se produit dans la base de données de production ne peut pas inexplicablement commencer à échouer vos tests, ET cela vous donne un point de départ pour ce qu'une "installation de base de données propre" "ressemblerait, ce qui est utile si vous devez déployer soudainement une nouvelle instance de l'application sans connexion à votre site de production existant.

Oui, j'utilise cette méthode moi-même, et oui, c'était un PITA à installer pour la PREMIÈRE fois, mais c'est plus que rentabilisé pendant la durée du projet que je suis sur le point de terminer. Il existe également un certain nombre d'outils qui facilitent les maquettes de données.

SplinterReality
la source
Cela semble être une bonne idée. J'aime particulièrement son aspect d'isolement complet; en commençant par une nouvelle installation de la base de données. Il semble que cela prendra un certain effort, mais une fois installé, ce sera très bénéfique. Merci.
Guven
3

C'est le problème éternel auquel tout le monde est confronté lors de l'écriture de tests d'intégration.

La solution idéale, en particulier si vous testez en production, est d'ouvrir une transaction dans la configuration et de la reculer dans le démontage. Je pense que cela devrait répondre à vos besoins.

Lorsque cela n'est pas possible, par exemple lorsque vous testez l'application à partir de la couche client, une autre solution consiste à utiliser une base de données sur une machine virtuelle et à prendre un instantané dans la configuration et à y revenir dans le démontage (il ne prendre aussi longtemps que vous pourriez vous y attendre).

pdr
la source
Je ne peux pas croire que je n'ai pas pensé à l'annulation de la transaction. J'utiliserai cette idée comme une solution rapide à la solution, mais finalement la base de données en mémoire semble très prometteuse.
Guven
3

Les tests d'intégration doivent toujours être exécutés par rapport à la configuration de production. Dans votre cas, cela signifie que vous devez avoir la même base de données et le même serveur d'applications. Bien sûr, pour des raisons de performances, vous pouvez décider d'utiliser une base de données en mémoire.

Ce que vous ne devriez jamais faire, c'est étendre la portée de votre transaction. Certaines personnes ont suggéré de prendre le contrôle de la transaction et de la annuler après les tests. Lorsque vous effectuez cette opération, toutes les entités (en supposant que vous utilisez JPA) restent attachées au contexte de persistance tout au long de l'exécution du test. Cela peut entraîner des bugs très désagréables qui sont très difficiles à trouver .

Au lieu de cela, vous devez effacer la base de données manuellement après chaque test via une bibliothèque comme JPAUnit ou similaire à cette approche (effacer toutes les tables à l'aide de JDBC).

En raison de vos problèmes de performances, vous ne devez pas exécuter les tests d'intégration sur chaque build. Laissez votre serveur d'intégration continue le faire. Si vous utilisez Maven, vous pouvez profiter du plug-in à sécurité intégrée qui vous permet de séparer vos tests en tests unitaires et d'intégration.

De plus, vous ne devriez rien vous moquer. N'oubliez pas que vous testez l'intégration, c'est-à-dire testez le comportement dans l'environnement d'exécution au moment de l'exécution.

BenR
la source
Bonne réponse. Un point: il peut être acceptable de se moquer de certaines dépendances externes (par exemple, l'envoi d'e-mails). Mais vous feriez mieux de vous moquer en mettant en place un service / serveur de simulation complet, et non de le simuler dans le code Java.
sleske
Sleske, je suis d'accord avec toi. Surtout lorsque vous utilisez JPA, EJB, JMS ou implémentez par rapport à une autre spécification. Vous pouvez échanger le serveur d'applications, le fournisseur de persistance ou la base de données. Par exemple, vous pouvez utiliser Glassfish et HSQLDB intégrés pour simplement configurer et améliorer la vitesse (vous êtes bien sûr libre de choisir une autre implémentation certifiée).
BenR
2

Sur l'évolutivité

J'ai eu ce problème quelques fois auparavant, que les tests d'intégration prenaient trop de temps à exécuter et n'étaient pas pratiques pour qu'un seul développeur s'exécute en continu dans une boucle de rétroaction étroite de modifications. Voici quelques stratégies pour y faire face:

  • Écrivez moins de tests d'intégration - si vous avez une bonne couverture avec des tests unitaires dans le système, les tests d'intégration devraient être plus axés sur les problèmes d'intégration (ahem), sur la façon dont les différents composants fonctionnent ensemble. Ils sont plus similaires aux tests de fumée, qui essaient simplement de voir si le système fonctionne toujours dans son ensemble. Donc, idéalement, vos tests unitaires couvrent la plupart des parties fonctionnelles de l'application, et les tests d'intégration vérifient simplement comment ces parties interagissent les unes avec les autres. Vous n'avez pas besoin d'une couverture étendue des chemins logiques ici, juste de certains chemins critiques à travers le système.
  • Exécutez uniquement un sous-ensemble de tests à la fois - en tant que développeur unique travaillant sur certaines fonctionnalités, vous avez normalement une idée des tests nécessaires pour couvrir cette fonctionnalité. Exécutez uniquement les tests qui ont du sens pour couvrir vos modifications. Des cadres comme JUnit vous permettent de regrouper les appareils dans une catégorie . Créez les regroupements qui permettent la meilleure couverture pour chaque fonctionnalité dont vous disposez. Bien sûr, vous voulez toujours exécuter tous les tests d'intégration à un moment donné, peut-être avant de vous engager dans le système de contrôle de source et certainement sur le serveur d'intégration continue.
  • Optimiser le système - Les tests d'intégration couplés à certains tests de performances peuvent vous fournir les informations nécessaires pour régler les parties lentes du système, afin que les tests s'exécutent plus rapidement plus tard. Cela peut aider à découvrir les index nécessaires dans la base de données, les interfaces de discussion entre les sous-systèmes ou d'autres goulots d'étranglement des performances qui doivent être adressés.
  • Exécutez vos tests en parallèle - Si vous disposez d'un bon regroupement de tests orthogonaux, vous pouvez essayer de les exécuter en parallèle . Soyez attentif à l' exigence orthogonale que j'ai mentionnée cependant, vous ne voulez pas que vos tests entrent dans les pieds les uns des autres.

Essayez de combiner ces techniques pour plus d'effet.

Jordão
la source
1
Vraiment de bonnes directives; écrire moins de tests d'intégration est à coup sûr le meilleur conseil. De plus, les exécuter en parallèle serait une très bonne alternative; un "test" parfait pour vérifier si mes tests se sont bien déroulés en termes d'isolement.
Guven
2

À des fins de test, nous avons utilisé un déploiement basé sur fichier d'une base de données SQLite (il suffit de copier une ressource). Cela a été fait afin que nous puissions également tester les migrations de schéma. Pour autant que je sache, les modifications de schéma ne sont pas transactionnelles et ne seront donc pas annulées après l'abrogation d'une transaction. Le fait de ne pas avoir à compter sur la prise en charge des transactions pour la configuration du test permet de tester correctement le comportement des transactions de votre application.

Carlo Kuip
la source