web-dev-qa-db-fra.com

Comment tester des scénarios os.exit dans Go

Compte tenu de ce code

func doomed() {
  os.Exit(1)
}

Comment puis-je tester correctement que l'appel de cette fonction entraînera une existence en utilisant go test? Cela doit se produire dans une suite de tests, en d'autres termes, l'appel os.Exit() ne peut pas avoir d'impact sur les autres tests et doit être intercepté.

29
mbrevoort

Il y a présentation par Andrew Gerrand (l'un des membres principaux de l'équipe Go) où il montre comment le faire.

Étant donné une fonction (dans main.go)

package main

import (
    "fmt"
    "os"
)

func Crasher() {
    fmt.Println("Going down in flames!")
    os.Exit(1)
}

voici comment le tester (via 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)
}

Le code invoque go test à nouveau dans un processus distinct via exec.Command, limitant l'exécution au test TestCrasher (via le -test.run=TestCrasher commutateur). Il passe également dans un drapeau via une variable d'environnement (BE_CRASHER=1) que la deuxième invocation vérifie et, s'il est défini, appelle le système en cours de test, retournant immédiatement après pour éviter de courir dans une boucle infinie. Ainsi, nous sommes renvoyés sur notre site d'appel d'origine et nous pouvons maintenant valider le code de sortie réel.

Source: diapositive 2 de la présentation d'Andrew. La deuxième diapositive contient également un lien vers la vidéo de présentation . Il parle de tests de sous-processus à 47:09

37
Timo Reimann

Je le fais en utilisant 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")
}

singe est super puissant quand il s'agit de ce genre de travail, et pour l'injection de fautes et autres tâches difficiles. Cela vient avec quelques mises en garde .

9
Allen Luce

Je ne pense pas que vous puissiez tester le os.Exit Sans simuler le test de l'extérieur (en utilisant exec.Command).

Cela dit, vous pourriez être en mesure d'atteindre votre objectif en créant une interface ou un type de fonction , puis en utilisant une implémentation noop dans vos tests:

Go Playground

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)
}
7
Matt Self

Vous ne pouvez pas, vous devez utiliser exec.Command et testez la valeur renvoyée.

1
OneOfOne

Code de test:

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

Code de test:

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
}
0
Alex Geer