web-dev-qa-db-fra.com

Tests unitaires en fonction du temps

J'ai besoin de tester une fonction dont le résultat dépendra de l'heure actuelle (en utilisant la fonction isBeforeNow() de Joda time, cela se produit).

    public boolean isAvailable() {
    return (this.someDate.isBeforeNow());
}

Est-il possible de bloquer/simuler l'heure du système avec (usin Mockito, par exemple) afin de pouvoir tester la fonction de manière fiable?

71
pojo

Joda time prend en charge la définition d'une heure actuelle "fausse" via les méthodes setCurrentMillisFixed et setCurrentMillisOffset de la classe DateTimeUtils.

Voir https://www.joda.org/joda-time/apidocs/org/joda/time/DateTimeUtils.html

61
Laurent Pireyn

La meilleure façon (IMO) de rendre votre code testable est d'extraire la dépendance de "quelle est l'heure actuelle" dans sa propre interface, avec une implémentation qui utilise l'heure système actuelle (utilisée normalement) et une implémentation qui vous permet de régler l'heure , avancez comme vous le souhaitez, etc.

J'ai utilisé cette approche dans diverses situations, et cela a bien fonctionné. C'est facile à configurer - il suffit de créer une interface (par exemple Clock) qui a une seule méthode pour vous donner l'instant actuel dans le format que vous voulez (par exemple en utilisant Joda Time, ou éventuellement un Date ).

124
Jon Skeet

Java 8 a introduit la classe abstraite Java.time.Clock qui vous permet d'avoir une implémentation alternative pour les tests. C'est exactement ce que Jon a suggéré dans sa réponse à l'époque.

19
xli

Pour ajouter à réponse de Jon Skeet , Joda Time contient déjà une interface d'heure actuelle: DateTimeUtils.MillisProvider

Par exemple:

import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils.MillisProvider;

public class Check {
    private final MillisProvider millisProvider;
    private final DateTime someDate;

    public Check(MillisProvider millisProvider, DateTime someDate) {
        this.millisProvider = millisProvider;
        this.someDate = someDate;
    }

    public boolean isAvailable() {
        long now = millisProvider.getMillis();
        return (someDate.isBefore(now));
    }
}

Simulez l'heure dans un test unitaire (en utilisant Mockito mais vous pouvez implémenter votre propre classe MillisProviderMock):

DateTime fakeNow = new DateTime(2016, DateTimeConstants.MARCH, 28, 9, 10);
MillisProvider mockMillisProvider = mock(MillisProvider.class);
when(mockMillisProvider.getMillis()).thenReturn(fakeNow.getMillis());

Check check = new Check(mockMillisProvider, someDate);

Utiliser l'heure actuelle en production ( DateTimeUtils.SYSTEM_MILLIS_PROVIDER a été ajouté à Joda Time en 2.9.3):

Check check = new Check(DateTimeUtils.SYSTEM_MILLIS_PROVIDER, someDate);
4

J'utilise une approche similaire à celle de Jon, mais au lieu de créer une interface spécialisée uniquement pour l'heure actuelle (disons, Clock), je crée généralement une interface de test spéciale (disons, MockupFactory). J'ai mis là toutes les méthodes dont j'ai besoin pour tester le code. Par exemple, dans l'un de mes projets, j'ai quatre méthodes:

  • celui qui renvoie un client de base de données maquette;
  • celui qui crée un objet notifiant maquette qui notifie le code des modifications dans la base de données;
  • celui qui crée une maquette Java.util.Timer qui exécute les tâches quand je le veux;
  • celui qui renvoie l'heure actuelle.

La classe testée a un constructeur qui accepte cette interface parmi d'autres arguments. Celui sans cet argument crée juste une instance par défaut de cette interface qui fonctionne "dans la vraie vie". L'interface et le constructeur sont tous deux privés du package, de sorte que l'API de test ne fuit pas en dehors du package.

Si j'ai besoin d'objets plus imités, j'ajoute simplement une méthode à cette interface et je l'implémente dans les implémentations de test et réelles.

De cette façon, je conçois un code adapté aux tests en premier lieu sans trop imposer au code lui-même. En fait, le code devient encore plus propre de cette façon, car une grande partie du code d'usine est collectée en un seul endroit. Par exemple, si j'ai besoin de passer à une autre implémentation de client de base de données en code réel, je n'ai qu'à modifier une seule ligne au lieu de rechercher des références au constructeur.

Bien sûr, tout comme dans le cas de l'approche de Jon, cela ne fonctionnera pas avec du code tiers que vous ne pouvez pas ou n'êtes pas autorisé à modifier.

1
Sergei Tachenov