如何在Go lang中模拟exec.Command进行多个单元测试?

时间:2017-08-21 03:58:51

标签: unit-testing go

我刚刚学习了使用exec.Command()的单元测试函数,即模拟exec.Command()。我继续添加更多单元案例,但遇到了无法模拟不同场景输出的问题。

以下是我试图测试的示例代码hello.go

package main

import (
    "fmt"
    "os/exec"
)

var execCommand = exec.Command

func printDate() ([]byte, error) {
    cmd := execCommand("date")
    out, err := cmd.CombinedOutput()
    return out, err
}

func main() {
    fmt.Printf("hello, world\n")
    fmt.Println(printDate())
}

以下是测试代码hello_test.go ...

package main

import (
    "fmt"
    "os"
    "os/exec"
    "testing"
)

var mockedExitStatus = 1
var mockedDate = "Sun Aug 20"
var expDate = "Sun Aug 20"

func fakeExecCommand(command string, args ...string) *exec.Cmd {
    cs := []string{"-test.run=TestHelperProcess", "--", command}
    cs = append(cs, args...)
    cmd := exec.Command(os.Args[0], cs...)
    cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
    return cmd
}

func TestHelperProcess(t *testing.T) {
    if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
        return
    }

    // println("Mocked Data:", mockedDate)
    fmt.Fprintf(os.Stdout, mockedDate)
    os.Exit(mockedExitStatus)
}

func TestPrintDate(t *testing.T) {
    execCommand = fakeExecCommand
    defer func() { execCommand = exec.Command }()

    out, err := printDate()
    print("Std out: ", string(out))
    if err != nil {
        t.Errorf("Expected nil error, got %#v", err)
    }
    if string(out) != expDate {
        t.Errorf("Expected %q, got %q", expDate, string(out))
    }
}

func TestPrintDateUnableToRunError(t *testing.T) {
    execCommand = fakeExecCommand
    defer func() { execCommand = exec.Command }()

    mockedExitStatus = 1
    mockedDate = "Unable to run date command"
    expDate = "Unable to run date command"

    out, err := printDate()
    print("Std out: ", string(out))
    if err != nil {
        t.Errorf("Expected nil error, got %#v", err)
    }
    if string(out) != expDate {
        t.Errorf("Expected %q, got %q", expDate, string(out))
    }
}
第二次测试

go test失败TestPrintDateUnableToRunError ...

$ go test hello
Std out: Sun Aug 20Std out: Sun Aug 20--- FAIL: TestPrintDateTomorrow (0.01s)
    hello_test.go:62: Expected "Unable to run date command", got "Sun Aug 20"
FAIL
FAIL    hello   0.017s

即使我尝试在测试用例中设置全局mockedDate值,它仍然会获得初始化的全局值。 全球价值未设定?或者,TestHelperProcess

中未更新对该全局变量的更改

2 个答案:

答案 0 :(得分:2)

我得到了解决方案......

  

全球价值未设定吗?或者,对于那个全局变量的更改没有在TestHelperProcess中更新?

由于在TestPrintDate()中调用了fakeExecCommand而不是exec.Command,并且调用fakeExecCommand只运行go test TestHelperProcess(),所以它总是一个新的只调用TestHelperProcess()的调用。由于仅调用TestHelperProcess(),因此未设置全局变量。

解决方案是在fakeExecCommand中设置Env,并在TestHelperProcess()中检索并返回这些值。

PS> TestHelperProcess已重命名为TestExecCommandHelper ,并且重命名的变量很少。

package main

import (
    "fmt"
    "os"
    "os/exec"
    "strconv"
    "testing"
)

var mockedExitStatus = 0
var mockedStdout string

func fakeExecCommand(command string, args ...string) *exec.Cmd {
    cs := []string{"-test.run=TestExecCommandHelper", "--", command}
    cs = append(cs, args...)
    cmd := exec.Command(os.Args[0], cs...)
    es := strconv.Itoa(mockedExitStatus)
    cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1",
        "STDOUT=" + mockedStdout,
        "EXIT_STATUS=" + es}
    return cmd
}

func TestExecCommandHelper(t *testing.T) {
    if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
        return
    }

    // println("Mocked stdout:", os.Getenv("STDOUT"))
    fmt.Fprintf(os.Stdout, os.Getenv("STDOUT"))
    i, _ := strconv.Atoi(os.Getenv("EXIT_STATUS"))
    os.Exit(i)
}

func TestPrintDate(t *testing.T) {
    mockedExitStatus = 1
    mockedStdout = "Sun Aug 201"
    execCommand = fakeExecCommand
    defer func() { execCommand = exec.Command }()
    expDate := "Sun Aug 20"

    out, _ := printDate()
    if string(out) != expDate {
        t.Errorf("Expected %q, got %q", expDate, string(out))
    }
}

func TestPrintDateUnableToRunError(t *testing.T) {
    mockedExitStatus = 1
    mockedStdout = "Unable to run date command"
    execCommand = fakeExecCommand
    defer func() { execCommand = exec.Command }()

    expDate := "Unable to run date command"

    out, _ := printDate()
    // println("Stdout: ", string(out))
    if string(out) != expDate {
        t.Errorf("Expected %q, got %q", expDate, string(out))
    }
}

go test结果如下...... (故意使一个测试失败,表明模拟工作正常)。

 go test hello
--- FAIL: TestPrintDate (0.01s)
        hello_test.go:45: Expected "Sun Aug 20", got "Sun Aug 201"
FAIL
FAIL    hello   0.018s

答案 1 :(得分:0)

根据您发布的代码,mockedDate变量不会执行任何操作。测试和printDate()的调用都没有使用它,因此TestPrintDateUnableToRunError()测试的执行方式就像之前的测试一样。

如果要向printDate()函数添加功能以返回“无法运行日期命令”字符串(在这种情况下),那么第62行的条件将通过。也就是说,当printDate()的返回值出错时,此类检查应该是不必要的。如果返回的错误是非nil,则返回的输出字符串应该是无效的(或为空,"")。

我不知道你真的希望printDate()失败,但就目前情况而言,它无法返回TestPrintDateUnableToRunError()中您期望的值。