unit test mock guide golang testing go exit-code

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:

Ir a la zona de juegos

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.