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.
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))
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))
...
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.
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
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.
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