testing - test - Cómo probar los escenarios de os.exit en Go
mockery golang (5)
Código para la prueba:
package main
import "os"
var my_private_exit_function func(code int) = os.Exit
func main() {
MyAbstractFunctionAndExit(1)
}
func MyAbstractFunctionAndExit(exit int) {
my_private_exit_function(exit)
}
Código de prueba:
package main
import (
"os"
"testing"
)
func TestMyAbstractFunctionAndExit(t *testing.T) {
var ok bool = false // The default value can be omitted :)
// Prepare testing
my_private_exit_function = func(c int) {
ok = true
}
// Run function
MyAbstractFunctionAndExit(1)
// Check
if ok == false {
t.Errorf("Error in AbstractFunction()")
}
// Restore if need
my_private_exit_function = os.Exit
}
Dado este código
func doomed() {
os.Exit(1)
}
¿Cómo pruebo correctamente que llamar a esta función resultará en una go test
uso de go test
? Esto debe ocurrir dentro de un conjunto de pruebas, en otras palabras, la llamada a os.Exit()
no puede afectar las otras pruebas y debe quedar atrapada.
Hay una presentación de Andrew Gerrand (uno de los miembros principales del equipo Go) donde muestra cómo hacerlo.
Dada una función (en main.go
)
package main
import (
"fmt"
"os"
)
func Crasher() {
fmt.Println("Going down in flames!")
os.Exit(1)
}
así es como lo probarías (a través de main_test.go
):
package main
import (
"os"
"os/exec"
"testing"
)
func TestCrasher(t *testing.T) {
if os.Getenv("BE_CRASHER") == "1" {
Crasher()
return
}
cmd := exec.Command(os.Args[0], "-test.run=TestCrasher")
cmd.Env = append(os.Environ(), "BE_CRASHER=1")
err := cmd.Run()
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
return
}
t.Fatalf("process ran with err %v, want exit status 1", err)
}
Lo que hace el código es invocar, go test
en un proceso separado a través de exec.Command
, limitando la ejecución a la prueba TestCrasher
(a través del -test.run=TestCrasher
). También pasa un indicador a través de una variable de entorno ( BE_CRASHER=1
) que la segunda invocación busca y, si está configurada, llama al sistema bajo prueba, regresando inmediatamente después para evitar que se ejecute en un bucle infinito. Por lo tanto, estamos siendo devueltos a nuestro sitio de llamada original y ahora podemos validar el código de salida real.
Fuente: Diapositiva 23 de la presentación de Andrew. La segunda diapositiva también contiene un enlace al video de la presentación . Habla de pruebas de subproceso a 47:09
Lo hago usando bouk / monkey :
func TestDoomed(t *testing.T) {
fakeExit := func(int) {
panic("os.Exit called")
}
patch := monkey.Patch(os.Exit, fakeExit)
defer patch.Unpatch()
assert.PanicsWithValue(t, "os.Exit called", doomed, "os.Exit was not called")
}
monkey es súper poderoso cuando se trata de este tipo de trabajo, y para la inyección de fallas y otras tareas difíciles. Viene con algunas advertencias .
No creo que pueda probar el os.Exit
real sin simular las pruebas del proceso externo (utilizando el exec.Command
).
Dicho esto, es posible que pueda lograr su objetivo creando una interfaz o tipo de función y luego use una implementación de noop en sus pruebas:
package main
import "os"
import "fmt"
type exiter func (code int)
func main() {
doExit(func(code int){})
fmt.Println("got here")
doExit(func(code int){ os.Exit(code)})
}
func doExit(exit exiter) {
exit(1)
}
No puede, tendría que usar exec.Command
y probar el valor devuelto.