Comment puis-je faire attendre un test JUnit?

93

J'ai un test JUnit que je souhaite faire attendre pendant un certain temps, de manière synchrone. Mon test JUnit ressemble à ceci:

@Test
public void testExipres(){
    SomeCacheObject sco = new SomeCacheObject();
    sco.putWithExipration("foo", 1000);
    //WAIT FOR 2 SECONDS
    assertNull(sco.getIfNotExipred("foo"));
}

J'ai essayé Thread.currentThread (). Wait (), mais cela lève une exception IllegalMonitorStateException (comme prévu). Y a-t-il une astuce ou ai-je besoin d'un autre moniteur?

Kylar
la source

Réponses:

117

Et pourquoi pas Thread.sleep(2000);? :)

Muel
la source
14
Si vous utilisez des outils d'analyse de code comme sonarqube, ils se plaindront de Thread.sleepdire quelque chose comme Utiliser Thread.sleep dans un test est généralement une mauvaise idée. Il crée des tests fragiles qui peuvent échouer de manière imprévisible en fonction de l'environnement ("Passe sur ma machine!") Ou de la charge. Ne vous fiez pas au timing (utilisez des simulations) ou n'utilisez pas de bibliothèques telles que Awaitility pour les tests asynchrones.
FuryFart
3
Cette réponse doit être supprimée et considérée comme nuisible. Une bien meilleure réponse ci-dessous est stackoverflow.com/a/35163873/1229735
yiati
71

Thread.sleep () pourrait fonctionner dans la plupart des cas, mais généralement si vous attendez, vous attendez en fait qu'une condition ou un état particulier se produise. Thread.sleep () ne garantit pas que tout ce que vous attendez s'est réellement produit.

Si vous attendez une demande de repos, par exemple, elle revient peut-être généralement dans 5 secondes, mais si vous définissez votre sommeil sur 5 secondes le jour où votre demande reviendra dans 10 secondes, votre test échouera.

Pour remédier à cela, JayWay a un excellent utilitaire appelé Awatility qui est parfait pour s'assurer qu'une condition spécifique se produit avant de passer à autre chose.

Il a également une belle api fluide

await().until(() -> 
{
    return yourConditionIsMet();
});  

https://github.com/jayway/awaitility

Ben Glasser
la source
Je ne pouvais pas attendre la compilation dans Android Studio.
IgorGanapolsky
Avez-vous ajouté cette ligne à votre fichier gradle: compile 'org.awaitility: awaitility: 3.0.0'?
Samoht
non. vous ajouteriez ceci dans un cas de test où vous voulez attendre une condition particulière
Ben Glasser
15

Si votre analyseur de code statique (comme SonarQube) se plaint, mais que vous ne pouvez pas penser à une autre façon, plutôt que de dormir, vous pouvez essayer avec un hack comme: Awaitility.await().pollDelay(Durations.ONE_SECOND).until(() -> true); C'est conceptuellement incorrect, mais c'est la même chose que Thread.sleep(1000).

Le meilleur moyen, bien sûr, est de passer un Callable, avec votre condition appropriée, plutôt que truece que j'ai.

https://github.com/awaitility/awaitility

rastaman
la source
@Jitendra: Notez qu'Awaitility avait l'habitude d'avoir la Durationclasse, mais depuis la version 4, ils l'ont renommée Durations.
Jacob van Lingen
Une mise en garde, si vous voulez que le délai d'interrogation soit de 10 secondes ou plus, assurez-vous d'augmenter le délai d'expiration à plus de 10 secondes car c'est le délai d'expiration par défaut et l'attendilité se plaindra que le délai d'expiration est inférieur au délai d'interrogation.
mise au point
12

Vous pouvez utiliser la bibliothèque java.util.concurrent.TimeUnit qui utilise Thread.sleep en interne. La syntaxe devrait ressembler à ceci:

@Test
public void testExipres(){
    SomeCacheObject sco = new SomeCacheObject();
    sco.putWithExipration("foo", 1000);

    TimeUnit.MINUTES.sleep(2);

    assertNull(sco.getIfNotExipred("foo"));
}

Cette bibliothèque fournit une interprétation plus claire de l'unité de temps. Vous pouvez utiliser «HEURES» / «MINUTES» / «SECONDES».

Akshay Pagar
la source
Si je démarre un thread de travail à partir d'un test, cela sleep()affecte-t-il le thread de travail?
Anthony Kong
6

Vous pouvez également utiliser l' CountDownLatchobjet comme expliqué ici .

Pierre Voisin
la source
0

Il y a un problème général: il est difficile de se moquer du temps. En outre, il est très mauvais de placer du code à exécution / attente longue dans un test unitaire.

Donc, pour rendre une API de planification testable, j'ai utilisé une interface avec une implémentation réelle et fictive comme celle-ci:

public interface Clock {

    public long getCurrentMillis();

    public void sleep(long millis) throws InterruptedException;

}

public static class SystemClock implements Clock {

    @Override
    public long getCurrentMillis() {
        return System.currentTimeMillis();
    }

    @Override
    public void sleep(long millis) throws InterruptedException {
        Thread.sleep(millis);
    }

}

public static class MockClock implements Clock {

    private final AtomicLong currentTime = new AtomicLong(0);


    public MockClock() {
        this(System.currentTimeMillis());
    }

    public MockClock(long currentTime) {
        this.currentTime.set(currentTime);
    }


    @Override
    public long getCurrentMillis() {
        return currentTime.addAndGet(5);
    }

    @Override
    public void sleep(long millis) {
        currentTime.addAndGet(millis);
    }

}

Avec cela, vous pouvez imiter le temps dans votre test:

@Test
public void testExipres() {
    MockClock clock = new MockClock();
    SomeCacheObject sco = new SomeCacheObject();
    sco.putWithExipration("foo", 1000);
    clock.sleep(2000) // WAIT FOR 2 SECONDS
    assertNull(sco.getIfNotExpired("foo"));
}

Une simulation multi-threading avancée Clockest bien sûr beaucoup plus complexe, mais vous pouvez la créer avec des ThreadLocalréférences et une bonne stratégie de synchronisation de l'heure, par exemple.

Dávid Horváth
la source