Comment tester les pipelines de canaux dans Go
J'utilise beaucoup le modèle "pipeline de canal" dans Go, qui ressemble à ceci:
// getSomeNums spits out ints onto a channel. Temperatures, pressures, doesn't matter
func getSomeNums(ch chan<- int) {
// Imagination goes here
}
// double takes numbers from in, doubles them, and pushes them into out
func double(in <-chan int, out chan<- int) {
for v := range in {
out <- v * 2
close(out)
}
source := make(chan int)
go getSomeNums(source)
doubles := make(chan int)
double(source, doubles)
Le problème que je rencontre à plusieurs reprises est que je dois tester un certain nombre de fonctionnalités différentes de ces fonctions de pipeline:
- Met une valeur sur le canal de sortie lorsque vous en mettez une sur le canal d'entrée
- Ne met pas de valeur sur le canal de sortie lorsque vous n'en mettez pas une sur le canal d'entrée
- Expiration du délai si le canal de sortie prend trop de temps après l'apparition d'une valeur sur le canal d'entrée
- Ferme le canal de sortie lorsque le canal d'entrée se ferme
- Ne ferme pas le canal de sortie avant que le canal d'entrée ne soit fermé
- Effectue la transformation correcte sur l'entrée
De plus, ce n'est qu'un exemple vraiment simple. Des cas plus typiques ressemblent à cet exemple, dans lequel nous essayons d'utiliser des capteurs de température redondants pour trouver des erreurs dans la sortie:
// Provided we have channels for sensorA, sensorB, and sensorC
import "math"
LIMIT = 0.1 // Set acceptable variation limit between sensors to 10%
type SafeTemp struct {
Temp float64
isSafe bool
}
// variation returns relative error between inputs. Unfortunately, "error" was taken
func variation(a, b float64) float64 {
return math.Abs((a - b) / (a + b))
}
// safify zips together temperatures so long as error is below LIMIT
func safify(chA, chB, chC <-chan float64, chOut chan<- SafeTemp) {
for {
a, aOk := <-chA
b, bOk := <-chB
c, cOk := <-chC
if !(aOk && bOk && cOk) {
close(chOut)
return
}
if variation(a, b) < LIMIT && variation(b, c) < LIMIT &&
variation(c, a) < LIMIT {
chOut <- SafeTemp{ (a + b + c) / 3, true }
} else {
chOut <- SafeTemp{ 0.0, false }
}
}
}
Maintenant, le nombre de choses que je dois tester pour la fonction pipeline ( safify
) augmente considérablement:
- Met une valeur sur le canal de sortie lorsque vous en obtenez une sur tous les canaux d'entrée
- Ne met pas de valeur sur le canal de sortie lorsque vous n'en avez pas sur tous les canaux d'entrée
- Expiration du délai si le canal de sortie prend trop de temps après les entrées sur les trois canaux d'entrée, mais seulement sur les trois
- Ferme le canal de sortie lorsqu'un canal d' entrée se ferme
- Ne ferme pas le canal de sortie si aucun canal d'entrée n'est fermé
- Indique que non
isSafe
si le premier canal varie considérablement des autres, avec des délais d'expiration - Indique que le
isSafe
deuxième canal diffère considérablement des autres, avec des délais d'attente - Indique que le
isSafe
troisième canal diffère considérablement des autres, avec des délais d'attente - Drapeaux que pas
isSafe
si tous les canaux varient considérablement d'autres, avec les délais d' attente
En outre, les trois canaux d'entrée peuvent se désynchroniser les uns avec les autres, ce qui ajoute une complexité significative encore au-delà de celle indiquée ci-dessus.
Il semble que beaucoup de ces vérifications (sauf spécifiquement celles qui ont à voir avec des calculs corrects) sont communes à pratiquement toutes les fonctions de pipeline de canaux de style fan dans Go, et le problème d'arrêt garantit que nous devons utiliser des délais d'expiration pour tous. de ces opérations, sauf si nous voulons que l'arrêt de nos tests dépende de l'arrêt et du comportement éventuel de poussée de canal des fonctions testées.
Compte tenu de la similitude de ces types de tests dans tous les domaines et de la façon dont je finis par écrire des tests assez similaires, essentiellement pour tester la conformité de ces fonctions de pipeline de canaux aux garanties de base des fonctions de pipeline de canaux , au lieu du comportement des fonctions , encore et encore et encore. , y a-t-il soit:
- Un ensemble standard de pratiques autour de ces types de tests de fiabilité des fonctions de pipeline de canaux OU
- Un cadre standard ou bien renforcé ou un ensemble de cadres pour tester les fonctions natives des canaux?
Réponses
Vous mélangez deux préoccupations différentes. Si vous avez créé une abstraction distincte pour le pipeline, vous pouvez la tester une fois. Quelque chose comme (pardonnez la syntaxe, je ne sais pas, allez):
func double(v int) int {
return v * 2
}
pipeline(in, out, double)
ou
func safe(v [3]float64) SafeTemp {
if variation(v[0], v[1]) < LIMIT && variation(v[1], v[2]) < LIMIT &&
variation(v[2], v[0]) < LIMIT {
return SafeTemp{ (v[0] + v[1] + v[2]) / 3, true }
} else {
return SafeTemp{ 0.0, false }
}
}
pipeline(in, out, safe)
Sans polymorphisme paramétrique, vous ne pouvez pas vraiment faire une pipeline
abstraction entièrement générique , vous devez donc accepter une certaine quantité de duplication. Cependant, vous devez au moins pouvoir séparer les préoccupations du modèle de pipeline de la logique plus spécifique à l'application.