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

报告相同问题?

悬赏问题

  • ¥15 用三极管设计一个单管共射放大电路
  • ¥20 fluent无法启动
  • ¥15 孟德尔随机化r语言运行问题
  • ¥15 pyinstaller编译的时候出现No module named 'imp'
  • ¥15 nirs_kit中打码怎么看(打码文件是csv格式)
  • ¥15 怎么把多于硬盘空间放到根目录下
  • ¥15 Matlab问题解答有两个问题
  • ¥15 LCD12864中文显示
  • ¥15 在使用CH341SER.EXE时不小心把所有驱动文件删除了怎么解决
  • ¥15 gsoap生成onvif框架