du0173 2017-08-21 03:58
浏览 67
已采纳

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

I just learnt unit testing functions that uses exec.Command() i.e., mocking exec.Command(). I went ahead to added more unit cases, but running into issues of not able to mock the output for different scenarios.

Here is a sample code hello.go I'm trying to test...

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
")
    fmt.Println(printDate())
}

Below is the test code 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 fails for the second 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

Even though I'm trying to set the global mockedDate value inside the test case, it's still getting the global value that it was initialized with. Is the global value not getting set? Or the changes to that global var is not getting updated in TestHelperProcess?

  • 写回答

2条回答 默认 最新

  • douyi1982 2017-08-21 18:30
    关注

    I got the solution for this...

    Is the global value not getting set? Or the changes to that global var is not getting updated in TestHelperProcess?

    Since in TestPrintDate(), fakeExecCommand is called instead of exec.Command, and calling fakeExecCommand runs go test to run only TestHelperProcess(), it's altogether a new invocation where only TestHelperProcess() will be executed. Since only TestHelperProcess() is called, the global variables aren't being set.

    The solution would be to set the Env in the fakeExecCommand, and retrieve that in TestHelperProcess() and return those values.

    PS> TestHelperProcess is renamed to TestExecCommandHelper, And few variables are renamed.

    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 results as below... (Purposely failing one test to show that the mock is working properly).

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

报告相同问题?

悬赏问题

  • ¥20 机器学习能否像多层线性模型一样处理嵌套数据
  • ¥20 西门子S7-Graph,S7-300,梯形图
  • ¥50 用易语言http 访问不了网页
  • ¥50 safari浏览器fetch提交数据后数据丢失问题
  • ¥15 matlab不知道怎么改,求解答!!
  • ¥15 永磁直线电机的电流环pi调不出来
  • ¥15 用stata实现聚类的代码
  • ¥15 请问paddlehub能支持移动端开发吗?在Android studio上该如何部署?
  • ¥20 docker里部署springboot项目,访问不到扬声器
  • ¥15 netty整合springboot之后自动重连失效