Cómo probar canalizaciones de canal en Go

Aug 15 2020

Utilizo mucho el patrón "canalización de canal" en Go, que se parece a esto:

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

El problema con el que me encuentro repetidamente es que tengo que probar varias características diferentes de estas funciones de canalización:

  • Pone un valor en el canal de salida cuando pones uno en el canal de entrada
  • No pone un valor en el canal de salida cuando no pone uno en el canal de entrada
  • Se agota el tiempo de espera si el canal de salida tarda demasiado después de que aparece un valor en el canal de entrada
  • Cierra el canal de salida cuando se cierra el canal de entrada
  • No cierra el canal de salida antes de cerrar el canal de entrada
  • Realiza la transformación correcta en la entrada.

Además, este es solo un ejemplo realmente simple. Los casos más típicos se parecen a este ejemplo, en el que intentamos usar sensores de temperatura redundantes para encontrar errores en la salida:

// 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 }
        }

    }
}

Ahora la cantidad de cosas que tengo que probar para la función de canalización ( safify) aumenta significativamente:

  • Pone un valor en el canal de salida cuando obtiene uno en todos los canales de entrada
  • No pone un valor en el canal de salida cuando no obtiene uno en todos los canales de entrada
  • Se agota el tiempo si el canal de salida tarda demasiado después de las entradas en los tres canales de entrada, pero solo los tres
  • Cierra el canal de salida cuando se cierra cualquier canal de entrada
  • No cierra el canal de salida si no hay ningún canal de entrada cerrado
  • Señala como no isSafesi el primer canal varía significativamente de otros, con tiempos de espera
  • Señala como no isSafesi el segundo canal varía significativamente de otros, con tiempos de espera
  • Señala como no isSafesi el tercer canal varía significativamente de otros, con tiempos de espera
  • Señala como no isSafesi todos los canales varían significativamente de otros, con tiempos de espera

Además, los tres canales de entrada pueden desincronizarse entre sí, lo que agrega una complejidad significativa aún más allá de la que se muestra arriba.

Parece que muchas de estas comprobaciones (excepto específicamente las que tienen que ver con cálculos correctos) son comunes básicamente a cualquier función de canalización de canal de estilo de ventilador en Go, y el problema de detención garantiza que tenemos que usar tiempos de espera para todos. de estas operaciones, a menos que queramos que la detención de nuestras pruebas dependa de la detención y eventual comportamiento de empuje de canal de las funciones que se están probando.

Dado lo similares que son estos tipos de pruebas en todos los ámbitos, y cómo termino escribiendo pruebas bastante similares, esencialmente probando qué tan bien estas funciones de canalización de canal se ajustan a las garantías básicas de función de canalización de canal , en lugar del comportamiento de las funciones , una y otra y otra vez , hay:

  1. Un conjunto estándar de prácticas en torno a este tipo de pruebas de confiabilidad de funciones de canalización O
  2. ¿Un marco estándar o bien reforzado o un conjunto de marcos para probar funciones nativas del canal?

Respuestas

2 KarlBielefeldt Aug 16 2020 at 18:27

Estás mezclando dos preocupaciones diferentes. Si hiciste una abstracción separada para la canalización, podrías probarla una vez. Algo como (perdona la sintaxis, no sé ir):

func double(v int) int {
    return v * 2
}

pipeline(in, out, double)

o

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)

Sin polimorfismo paramétrico, realmente no puede hacer una pipelineabstracción completamente genérica , por lo que debe aceptar una cierta cantidad de duplicación. Sin embargo, debería poder al menos separar las preocupaciones del patrón de canalización de la lógica más específica de la aplicación.