[JAVA] Je veux être finalement même à kotlin

Il est devenu courant d'automatiser partiellement les tests. Il semble que certaines personnes utilisent parfois Thread.sleep () pour attendre que le navigateur fonctionne. attends une minute. Cela ne change-t-il pas en fonction du temps de sommeil et de l'environnement d'exécution? Si vous voulez tester le processus d'attente pendant un temps fixe, vous pouvez utiliser Thread.sleep (). Cependant, je pense que le temps de sommeil est ajusté sur une base ad hoc, comme "Cela dépend du réseau combien de temps cela prendra, mais cela ne fonctionnera que si vous attendez environ 2 secondes."

J'ai écrit plus tôt Comment attendre le dessin d'écran dans Selenium. A cette époque, je voulais utiliser normalement {} qui était dans ScalaTest en Java, donc je l'ai implémenté en Java. Je veux l'utiliser dans kotlin cette fois, non, je peux appeler des classes et des méthodes Java de kotlin, mais je pense que si je l'écris en kotlin, ce sera plus beau. J'utilise également vavr. Contrairement à la dernière fois, voyons comment étoffer après avoir écrit la partie de base.

Pensez à l'interface

Ici, au lieu de l'interface Java / Kotlin, nous examinerons quel type de formulaire chaque année devrait être facile à utiliser. Lorsque vous l'utilisez, vous voulez vous sentir libre de l'utiliser comme ça.

driver.get(url) 
eventually {
    //L'ouverture de l'url prend du temps, une erreur se produira donc immédiatement après.
    assert(driver.findElementById("id"))
}

Laissons simplement les parenthèses augmenter lors de la spécification de la date limite et du temps d'attente. Vous pouvez préparer un délai d'expiration par défaut, généralement sans parenthèses, et le spécifier uniquement lorsque vous devez absolument le spécifier.

broswer.login()
eventually ({
    assert(browser.contents().contains(data))
}, Duration.ofSeconds(2), Duration.ofSeconds(1))

finalement mis en œuvre

Puisque kotilin a un argument par défaut, vous n'avez pas à l'utiliser pour créer une méthode similaire comme Java avec des arguments différents.

object Eventually {
    val DEFAULT_TIMEOUT = Duration.ofSeconds(10)
    val DEFAULT_INTERVAL = Duration.ofSeconds(3)

    fun <R> eventually(f: () -> R, timeout: Duration = DEFAULT_TIMEOUT, interval: Duration = DEFAULT_INTERVAL): R {
...

Cependant, cette interface nécessitait à la fois des parenthèses et des crochets ondulés, même sans délai, comme suit:

eventually ({
    // ...
})

Je ne peux pas m'en empêcher, alors je vais en préparer un autre.

    fun <R> eventually(f: () -> R) = Eventually.eventually(f, DEFAULT_TIMEOUT, DEFAULT_INTERVAL)
    fun <R> eventually(f: () -> R, timeout: Duration = DEFAULT_TIMEOUT, interval: Duration = DEFAULT_INTERVAL): R {
...

Vous pouvez désormais appeler l'un des éléments suivants:

eventually { ... }
eventually ({ ... }, timeout, interval)
...

Bien sûr, puisqu'il s'agit de kotlin, vous pouvez également spécifier le nom de l'argument.

//Je souhaite spécifier uniquement l'intervalle
eventually ({ ... }, interval=Duration.ofSeconds(1))
...

Implémentation logique

Tout d'abord, considérons une fonction qui "traite f () et réessaye si une exception se produit".

fun <R> tryExecute(f: () -> R):R {
    return try {
        f()
    } catch (t: Throwable) {
        tryExecute(f)
    }
}

Maintenant, vous avez une fonction qui boucle indéfiniment tant que vous continuez à obtenir des erreurs. Fixons une date butoir car c'est un problème si cela ne s'arrête pas indéfiniment. Ici, nous utilisons java.time.Instant. Je souhaite spécifier le délai d'expiration dans Durée, mais je l'expliquerai plus tard. Pourquoi devrais-je ajouter tailrec pendant que je suis allongé?

tailrec fun <R> tryExecute(f: () -> R, until: Instant):R {
    if(now()>until) throw RuntimeException("je ne peux pas le faire")
    return try {
        f()
    } catch (t: Throwable) {
        Thread.sleep(interval)
        tryExecute(f, until)
    }
}

Si f () lève une exception immédiatement, un CPU (tout dépendant de l'implémentation de f ()) sera utilisé jusqu'à la date limite, et si une erreur se produit, il attendra pendant un certain temps. Cette fois, nous utiliserons java.time.Duration pour représenter la période.

tailrec fun <R> tryExecute(f: () -> R, until: Instant, interval: Duration):R {
    if(now()>until) throw RuntimeException("je ne peux pas le faire")
    return try {
        f()
    } catch (t: Throwable) {
        Thread.sleep(interval)
        tryExecute(f, until, interval)
    }
}

Maintenant, vous voulez l'exception que f () lève. Requis pour le débogage. Passons l'exception qui s'est produite lors de la récurrence et renvoyons-la comme cause de l'exception lorsqu'elle expire finalement. La définition de l'argument s'allonge, je vais donc en faire un seul caractère.

tailrec fun <R> tryExecute(f: () -> R, u: Instant, i: Duration, t: Throwable):R {
    if(now() > u) throw RuntimeException("je ne peux pas le faire", t)
    return try {
        f()
    } catch (t: Throwable) {
        Thread.sleep(interval)
        tryExecute(f, u, i, t)
    }
}

Maintenant, quand il expire, cause () récupère la dernière exception que vous avez interceptée. La logique est maintenant terminée.

éventuellement () implémentation

Appelons la logique actuelle à partir de la méthode que nous venons de créer. Calculez la date d'expiration en ajoutant le délai d'expiration donné par Durée à l'heure actuelle.

val start = Instant.now()
val until = start.plusMillis(timeout.toMillis())

tryEvent prend la dernière exception interceptée comme argument, mais au début, aucune exception ne se produit et il est désagréable de passer null, alors assurez-vous de prendre Option et Option.none () Passons. Oh, cette option utilise vavr (anciennement javaslang). Je n'expliquerai pas cela cette fois.


    tailrec fun <R> tryExecute(f: () -> R, u: Instant, i: Duration, t: Option<Throwable>): R {
        if (Instant.now() > u) throw t.getOrElse(TimeoutException())
        return try {
            f()
        } catch (t: Throwable) {
            Thread.sleep(i.toMillis())
            tryExecute(f, u, i, Option.some(t))
        }
    }

tryExecute(f, until, interval, Option.none())

TimeoutException est maintenant levée si un délai d'attente se produit avant qu'une exception ne se produise. Ainsi, lorsque vous détectez cette exception, créez un message d'erreur avec le temps écoulé.

        //Votre interlocuteur
        try {
            tryExecute(f, until, interval, Option.none())
        } catch (t: Throwable) {
            throw createException(start, t)
        }

Fonction de composition de message. replace convertira la chaîne de caractères s en valeur s'il y a une clé dans la carte. C'est récursif, mais vous pouvez le plier avec map.fold.

    fun replace(s: String, m: Map<String, String>): String =
        if (m.isEmpty) s else replace(s.replace(":" + m.head()._1, m.head()._2), m.tail())

    private fun createException(start: Instant, t: Throwable): Throwable {
        val messageMap = HashMap.ofEntries<String, String>(
                Tuple2("time", Duration.between(start, Instant.now()).toString()),
                Tuple2("message", t.message)
        )
        return RuntimeException(replace(MESSAGE_TEMPLATE, messageMap), t)
    }

C'est tout ce qu'on peut en dire. Le tout est comme ça.

Eventually.kt


import io.vavr.Tuple2
import io.vavr.collection.HashMap
import io.vavr.collection.Map
import io.vavr.control.Option
import java.time.Duration
import java.time.Instant
import java.util.concurrent.TimeoutException

object Eventually {
    val DEFAULT_TIMEOUT = Duration.ofSeconds(10)
    val DEFAULT_INTERVAL = Duration.ofSeconds(3)

    val MESSAGE_TEMPLATE = "Eventually failed over :time. Last message is:\n:message";
    internal tailrec fun replace(s: String, m: Map<String, String>): String =
        if (m.isEmpty) s 
        else replace(s.replace(":" + m.head()._1, m.head()._2), m.tail())

    private tailrec fun <R> tryExecute(f: () -> R, u: Instant, i: Duration, t: Option<Throwable>): R {
        if (Instant.now() > u) throw t.getOrElse(TimeoutException())
        return try {
            f()
        } catch (t: Throwable) {
            Thread.sleep(i.toMillis())
            tryExecute(f, u, i, Option.some(t))
        }
    }

    private fun createException(start: Instant, t: Throwable): Throwable {
        val messageMap = HashMap.ofEntries<String, String>(
                Tuple2("time", Duration.between(start, Instant.now()).toString()),
                Tuple2("message", t.message)
        )
        return RuntimeException(replace(MESSAGE_TEMPLATE, messageMap), t)
    }

    fun <R> eventually(f: () -> R) = Eventually.eventually(f, DEFAULT_TIMEOUT, DEFAULT_INTERVAL)
    fun <R> eventually(f: () -> R, timeout: Duration = DEFAULT_TIMEOUT, interval: Duration = DEFAULT_INTERVAL): R {
        val start = Instant.now()
        val until = start.plusMillis(timeout.toMillis())
        return try {
            tryExecute(f, until, interval, Option.none())
        } catch (t: Throwable) {
            throw createException(start, t)
        }
    }

Au début, j'ai essayé d'utiliser io.vavr.kotlin.Try sans utiliser try {}, et essayé et erreur, et tryEvent () a renvoyé Either <R, Throwable>. Je l'ai changé parce qu'il était plus propre. Cependant, le nombre de lignes n'a pas beaucoup diminué par rapport à la version Java. S'il vous plaît laissez-moi savoir s'il existe une meilleure façon.

tester

En fait, nous testerons chaque fonction lors de sa mise en œuvre. Par exemple, lorsque vous écrivez replace (), vérifiez si cette partie fonctionne comme prévu. Puisque replace peut être privée, vous pouvez supprimer testReplace () après avoir confirmé que replace () est appelée dans le test final. Dans ce cas, changez ʻinternal fun replace () en private fun replace () `. Je le laisse ici.

EventuallyTest.kt


import <src>.Eventually.eventually
import <src>.Eventually

import io.vavr.Tuple2
import io.vavr.collection.HashMap
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Test
import org.slf4j.LoggerFactory
import java.lang.IllegalArgumentException
import java.time.Duration

typealias e = Tuple2<String, String>

class EventuallyTest {
    private val log = LoggerFactory.getLogger(this.javaClass)

    @Test
    fun testReplace() {
        val r = Eventually.replace("this is :1, :2, :x", HashMap.ofEntries(
                e("1", "changed"),
                e("2", "zzzzz"),
                e("x", "yyyyy")
        ))
        log.info(r)
        assertEquals("this is changed, zzzzz, yyyyy", r)
    }

    @Test
    fun testEventually() {
        val r = eventually {
            log.info("aaa")
            "a"
        }
        assertEquals("a", r)
    }

    @Test
    fun testEventually2Sec() {
        try {
            eventually({
                log.info("aaa")
                throw IllegalArgumentException("x")
            }, timeout = Duration.ofSeconds(2))
        }catch (e: Exception){
            assertEquals("x", e.cause!!.message)
        }
    }
}

KotlinTest

Apparemment Kotlin Test [éventuellement](https://github.com/kotlintest/kotlintest/blob/master/kotlintest-assertions/src/jvmMain/kotlin/io/kotlintest/ Finalement, kt) semble exister. Il y a aussi FunSpec etc. et il semble pratique à utiliser comme ScalaTest. La relation de package de KotlinTest elle-même est compliquée et l'obstacle semble être élevé, mais s'il peut être introduit, c'est également une bonne chose. Il existe également une méthode d'extension pour arrow.

Recommended Posts

Je veux être finalement même à kotlin
Je souhaite utiliser les fonctions pratiques de Clojure dans Kotlin
Même en Java, je veux afficher true avec un == 1 && a == 2 && a == 3
Je veux créer un fichier Parquet même en Ruby
Je veux utiliser @Autowired dans Servlet
Je souhaite envoyer un e-mail en Java.
Même en Java, je veux afficher true avec un == 1 && a == 2 && a == 3 (édition PowerMockito)
Je souhaite transmettre APP_HOME pour me connecter à Gradle
rsync4j --Je veux toucher rsync en Java.
Je veux obtenir la valeur en Ruby
Même en Java, je veux sortir true avec un == 1 && a == 2 && a == 3 (deuxième décoction Javassist)
Même en Java, je veux afficher true avec un == 1 && a == 2 && a == 3 (Black Magic)
Je veux faire quelque chose comme "cls" en Java
Je veux intégrer n'importe quel TraceId dans le journal
Je veux aussi utiliser des coquillages à Laradock! !!
Je veux aussi utiliser ES2015 avec Java! → (´ ・ ω ・ `)
Je souhaite définir une fonction dans la console Rails
Je veux faire des transitions d'écran avec kotlin et java!
Je veux arrêter les cas de serpent avec des définitions de table
Je veux cliquer sur une broche GoogleMap dans RSpec
Je veux convertir des caractères ...
[Active Admin] Je souhaite spécifier l'étendue de la collection à afficher dans select_box
Je veux trouver un chemin relatif dans une situation où Path est utilisé
Je souhaite effectuer une factorisation prime rapide avec Ruby (ABC177E)
Je veux faire une liste avec kotlin et java!
Je veux créer une fonction avec kotlin et java!
J'ai essayé d'implémenter une application web pleine de bugs avec Kotlin
Je souhaite passer au même écran dans l'état enregistré
Je veux implémenter diverses fonctions avec kotlin et java!
Je souhaite simplifier l'instruction if-else de la branche conditionnelle en Java
[Moteur CQ] Je souhaite gérer des collections comme Stream ou .Net LINQ même dans Java 7.
Je veux FlashAttribute au printemps même si j'ai défini un proxy inverse! (ne pas faire)
Je veux pouvoir penser et écrire moi-même des expressions régulières. ..
Je veux revenir à l'écran précédent avec kotlin et java!
Je veux obtenir des propriétés sous forme de chaînes JSON dans Jackson!
Je veux ajouter un appareil dans Rails, mais je ne peux pas grouper l'installation
[Java] Je veux effectuer distinctement avec la clé dans l'objet
Je veux changer la valeur de l'attribut dans Selenium of Ruby
[Android] Je souhaite obtenir l'auditeur à partir du bouton de ListView
Je souhaite développer une application web!
[Rails] Je souhaite envoyer des données de différents modèles dans un formulaire
Je veux écrire une JSP dans Emacs plus facilement que la valeur par défaut.
Je veux écrire un joli build.gradle
Je souhaite éliminer les messages d'erreur en double
Je veux créer une application ios.android
Je souhaite sélectionner plusieurs éléments avec une disposition personnalisée dans la boîte de dialogue
Je souhaite utiliser DBViewer avec Eclipse 2018-12! !!
Je veux écrire un test unitaire!
(Limité à Java 7 ou version ultérieure) Je souhaite que vous compariez des objets dans Objects.equals