Vos tests ont besoin d'amour – Liskov Substitution Principle – Exploitez les outils



Vos tests ont besoin d'amour – Liskov Substitution Principle – Exploitez les outils

0 0


your-tests-need-love

Vos tests ont besoin d'amour

On Github bsautel / your-tests-need-love

Vos tests ont besoin d'amour

Soyez testophiles. Personnes qui aiment les tests et leur donnent de l'amour. Questions à la fin.

Benoit Sautel

Développeur passionné Evangélisaion en génie logciel

Tester

  • Toute l'industrie doit tester. L'humain n'est pas infaillible.
  • Aéronautique pour des raisons de sécurité et de maintenance des avions
  • Cas nominal / cas limites
  • Si nous ne testons pas, bugs à répétition, productivité diminue petit à petit. On finit par tout jeter et refaire.
  • Pourtant, la dématérialisation nous aide. Nous avons la possibilité de tester à moindre coût et automatiquement.

Quand tester devient une corvée

  • De base le développeur n'aime pas tester
  • Pas prioritaire parce que n'apporte pas de valeur directe
  • Vite fait mal fait
  • Résultat : les tests font perdre du temps. Pas rentables
  • Instables, lents, personne ne s'en occupe

Tester, ça s'apprend

Ce n'est pas une fatalité. Apprendre techniques / méthodes.

Tester du code testable

Le code n'est pas testable par nature

Réutilisable

Code testable = code réutilisable

SOLID

Les 5 principes fondamentaux de la programmation orientée objet Un principe par lettre. Pas dans l'ordre.

Single Responsibility Principle

Une classe ne doit faire qu'une seule chose
  • Une classe n'a pas plus d'une raison de changer
  • Un seul niveau d'abstraction par classe
  • Beaucoup de délégation
  • Nommage simple et explicite
  • Peu de méthodes publiques (idéalement une)
  • 100 lignes maximum
  • Facilement réutilisable
  • Difficulté pour nommer la classe autrement que XxxManager
  • Besoin de tester une méthode privée
  • Changements fréquents donc refactoring
  • Difficile à substituer dans un test
  • Vous trouvez que c'est beaucoup ? Objects calisthenics

Dependency Inversion Principle

Une classe ne doit pas avoir de couplage avec les implémentations de ses dépendances (délégation)

Conséquences

  • Demande toutes ses dépendances dans le constructeur
  • C'est à l'appelant de choisir les collaborateurs (inversion de contrôle)
  • Utilisable dans d'autres contextes
  • Attention aux fonctions ou méthodes statiques

Injection de dépendances

Liskov Substitution Principle

Un type doit toujours pouvoir être remplacé par un sous-type sans changer la sémantique du code

Conséquences

  • Héritage seulement si polymorphisme
  • L'héritage ne doit pas servir à partager du code

Non respect : Carré rectangle

Carré / Rectangle

public class Rectangle {
    private final int width;
    private final int length;

    public Rectangle(int width, int length) {
        this.width = width;
        this.length = length;
    }

    public int getLength() {
        return length;
    }

    public int getWidth() {
        return width;
    }
}

Carré / Rectangle

public class Square extends Rectangle {
    public Square(int length) {
        super(length, length);
    }
}

Carré / Rectangle

public class Rectangle {
    private int width;
    private int length;

    public Rectangle(int width, int length) {
        this.width = width;
        this.length = length;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }
}

Carré / Rectangle

public class Square extends Rectangle {
    public Square(int length) {
        super(length, length);
    }

    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        // What about the length?
    }

    @Override
    public void setLength(int length) {
        super.setLength(length);
        // What about the width?
    }
}

throw new UnsupportedOperationException();

Comportement / état

Fort couplage entre classes du même arbre d'héritage

En Java / PHP (modèle héritage unique):

  • Privilégier implements à extends
  • Eviter les classes abstraites, utiliser plutôt le pattern strategy

Interface segregation principle

Un client d'une classe ne doit pas dépendre de méthodes qu'il n'utilise pas

Open Closed Principle

Une classe doit être ouverte à l'extension mais fermée à la modification

Design patterns

Des tests efficaces

FIRST

Fast

Feedback rapide. Exécution de l'ensemble des tests aussi.

Independant

Autonome, ne dépend pas de l'exécution des autres. Pas de base de données par exemple. N'importe que ordre.

Repeatable

Marche en toutes circonstances (machine, charge, configuration...).

Self-validating

Pas d'intervention humaine nécessaire. Automatique.

Timely

Dans la même temporalité que le code associé. Pas un mois après.

Test Driven Development

  • Cas adaptés : algorithme
  • Cas moins adaptés : design au fur et à mesure du développement

Au plus près du code

  • Près du code testé
  • Ne pas avoir besoin de débugger pour comprendre où c'est cassé
  • Par expérience : code bien testé unitairement fonctionne
  • Tests d'intégration ok mais c'est plus compliqué / long

Simples et bien nommés

  • Should return the user when the user exists
  • Should return a not found error when the user does not exist
Parallèle avec le BDD (Behavior Driven Development)

Faire échouer le test

  • S'assurer que le test est bien joué
  • Modifier le code pour le faire échouer

Mock ou pas mock ?

Mock = magie. Ce n'est pas la solution à tout.

Solutions pour subsituer un objet:

  • Real object
  • Fake object
  • Stub
  • Mock

Code asynchrone

  • Eviter le code asynchrone
  • Si asynchrone, surtout ne pas faire la sieste !
  • Si c'est nécessaire proposer un moyen de savoir quand l'action est terminée
  • Utiliser des outils de test asynchrone pour faire de l'attente active

Exploitez les outils

  • Bases de données en mémoire
  • Rules JUnit : par exemple partage d'initialisation

Assertions

assertThat(france.getCapital()).isEqualTo("Paris");
assertThat(france.getPopulation())
        .isGreaterThan(65000000)
        .isLessThan(70000000);
List<String> biggestCities = france.getThreeBiggestCities();

// At least
assertThat(biggestCities).contains("Paris", "Marseille");

// Exactly
assertThat(biggestCities).containsExactly("Paris", "Marseille", "Lyon");

// Only but in any order
assertThat(biggestCities).containsOnly("Marseille", "Lyon", "Paris");
Exemple : AssertJ en Java

Couverture de code

  • Code effectivement exécuté pendant les tests
  • Couverture 100% pas forcément un objectif
  • Ligne converte n'implique pas ligne bien testée

Mutation de code

  • Que couvre vraiment mon code ?

Et dans la vraie vie ?

  • Mission commando
  • Code pas testable
  • Commencer par du code autonome, testable et testé
  • Ensuite vous aurez plus de recul pour comprendre ce qui va pas et le corriger
  • Vidéos de Sandro Mancuso et David Gageot

Nettoyer au fur et à mesure

  • Ensemble de bonnes pratiques qui permettent de faire du bon code.
  • Bon code = code testable entre autres

Présentation

Réalisée avec Reveal.js

Code source sur Github

Visible en ligne sur http://files.fierdecoder.fr/your-tests-need-love/

Références externes

Liens vers des pages web citées pendant la présentation.

Crédits photos

1/3

2/3

3/3