英文:
How to mock *exec.Cmd / exec.Command()?
问题
我需要模拟exec.Command()
函数。
我可以使用以下方式进行模拟:
var rName string
var rArgs []string
mockExecCommand := func(name string, arg ...string) *exec.Cmd {
rName = name
rArgs = arg
return nil
}
然而,在实际代码中,这种方式不起作用,因为它会报错空指针异常,因为返回的exec.Cmd
调用了Run()
函数。
我尝试了以下方式进行模拟:
type mock exec.Cmd
func (m *mock) Run() error {
return nil
}
var rName string
var rArgs []string
mockExecCommand := func(name string, arg ...string) *exec.Cmd {
rName = name
rArgs = arg
m := mock{}
return &m
}
但是它报错:cannot use &m (value of type *mock) as *exec.Cmd value in return statementcompilerIncompatibleAssign
。
有没有办法解决这个问题?有没有更好的方法来模拟exec.Command()
函数?
如果我返回一个"mock"命令,模拟函数是可以工作的,尽管我更希望能够控制Run()
函数:
var rName string
var rArgs []string
mockExecCommand := func(name string, arg ...string) *exec.Cmd {
rName = name
rArgs = arg
return exec.Command("echo")
}
英文:
I need to mock exec.Command()
.
I can mock it using:
var rName string
var rArgs []string
mockExecCommand := func(name string, arg ...string) *exec.Cmd {
rName = name
rArgs = arg
return nil
}
However, this won't work in the actual code, as it complains about the nil pointer, since the returning exec.Cmd
calls Run()
.
I tried to mock it like:
type mock exec.Cmd
func (m *mock) Run() error {
return nil
}
var rName string
var rArgs []string
mockExecCommand := func(name string, arg ...string) *exec.Cmd {
rName = name
rArgs = arg
m := mock{}
return &m
}
But it complains: cannot use &m (value of type *mock) as *exec.Cmd value in return statementcompilerIncompatibleAssign
.
Is there any way to approach this? Is there a better way to mock exec.Command()
?
The mocked function works if I return a "mock" command, although I'd prefer to control the Run()
function too:
var rName string
var rArgs []string
mockExecCommand := func(name string, arg ...string) *exec.Cmd {
rName = name
rArgs = arg
return exec.Command("echo")
}
</details>
# 答案1
**得分**: 3
虽然劫持测试可执行文件来运行特定函数是可行的,但更简单的方法是使用常规的依赖注入。不需要任何魔法。
设计一个接口(例如`CommandExecutor`),用于运行命令,然后将其中一个命令作为输入传递给需要运行命令的函数。在测试期间,您可以提供一个满足该接口的模拟实现(手工编写或使用您选择的工具,如GoMock)。对于生产代码,提供真正的实现(调用`exec`包)。您的模拟实现甚至可以对参数进行断言,以确保命令被正确地“执行”。
<details>
<summary>英文:</summary>
While hijacking the test executable to run a specific function works, it would be more straightforward to just use regular dependency injection. No magic required.
Design an interface (e.g. `CommandExecutor`) that can run commands, then take one of those as your input to whatever function needs to run a command. You can then provide a mock implementation that satisfies the interface (hand-crafted, or generated using your tool of choice, like GoMock) during your tests. Provide the real implementation (which calls into the `exec` package) for your production code. Your mock implementation can even make assertions on the arguments so that you know the command is being "executed" correctly.
</details>
# 答案2
**得分**: 2
有一种方法可以做到这一点。所有的功劳归功于这篇文章:[this](https://npf.io/2015/06/testing-exec-command/)。请查看该文章以了解下面的代码是如何工作的:
```go
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
}
os.Exit(0)
}
英文:
There is actually a way to do this. All credit goes to this article. Check it out for an explanation on what's going on below:
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
}
os.Exit(0)
}
答案3
得分: 1
我知道的在Go语言中使用多态性的最佳方法是使用多态。你的想法是正确的。我创建了一个详细的解释,你可以在https://github.com/schollii/go-test-mock-exec-command找到。当我搜索如何模拟os/exec
时,我只能找到另一个答案中提到的环境变量技术。这种方法绝对不是必需的,正如我在我提供的git仓库的自述文件中所提到的,只需要一点多态性就可以实现。
总结如下:
- 创建一个
exec.Cmd
的接口类,只包含应用程序(或模块)代码中需要使用的必要方法。 - 创建一个实现该接口的结构体,例如可以只提及
exec.Cmd
。 - 创建一个指向从步骤2返回的结构体的包级别变量(导出的变量)。
- 让你的应用程序代码使用该包级别变量。
- 让你的测试创建一个新的实现该接口的结构体,但只包含输出和退出码,并让测试用例将该包级别变量替换为该新结构体的实例。
在应用程序代码中,它看起来像这样:
type IShellCommand interface {
Run() error
}
type execShellCommand struct {
*exec.Cmd
}
func newExecShellCommander(name string, arg ...string) IShellCommand {
execCmd := exec.Command(name, arg...)
return execShellCommand{Cmd: execCmd}
}
// 在测试中覆盖此函数以模拟git shell命令
var shellCommander = newExecShellCommander
func myFuncThatUsesExecCmd() {
cmd := shellCommander("git", "rev-parse", "--abbrev-ref", "HEAD")
err := cmd.Run()
if err != nil {
// 处理错误
} else {
// 处理输出
}
}
在测试代码中,它看起来像这样:
type myShellCommand struct {
RunnerFunc func() error
}
func (sc myShellCommand) Run() error {
return sc.RunnerFunc()
}
func Test_myFuncThatUsesExecCmd(t *testing.T) {
// 临时替换shell commander
curShellCommander := shellCommander
defer func() { shellCommander = curShellCommander }()
shellCommander = func(name string, arg ...string) IShellCommand {
fmt.Printf("exec.Command() for %v called with %v and %v\n", t.Name(), name, arg)
return myShellCommand{
RunnerFunc: func() error {
return nil
},
}
}
// 现在shellCommander被模拟了,调用我们想要测试的函数:
myFuncThatUsesExecCmd()
// 进行检查
}
英文:
The best way that I know of in go is to use polymorphism. You were on the right track. A detailed explanation is at https://github.com/schollii/go-test-mock-exec-command, which I created because when I searched for how to mock os/exec
, all I could find was the env variable technique mentioned in another answer. That approach is absolutely not necessary, and as I mention in the readme of the git repo I linked to, all it takes is a bit of polymorphism.
The summary is basically this:
- Create an interface class for
exec.Cmd
that has only the necessary methods to be used by your application (or module) code - Create a struct that implements that interface, eg it can just mention
exec.Cmd
- Create a package-level var (exported) that points to a function that returns the struct from step 2
- Make your application code use that package-level var
- Make your test create a new struct that implements that interface, but contains only outputs and exit codes, and make the test replace that package-level var by an instance of this new struct
It will look something like this in the application code:
type IShellCommand interface {
Run() error
}
type execShellCommand struct {
*exec.Cmd
}
func newExecShellCommander(name string, arg ...string) IShellCommand {
execCmd := exec.Command(name, arg...)
return execShellCommand{Cmd: execCmd}
}
// override this in tests to mock the git shell command
var shellCommander = newExecShellCommander
func myFuncThatUsesExecCmd() {
cmd := shellCommander("git", "rev-parse", "--abbrev-ref", "HEAD")
err := cmd.Run()
if err != nil {
// handle error
} else {
// process & handle output
}
}
On the test side it will look something like this:
type myShellCommand struct {
RunnerFunc func() error
}
func (sc myShellCommand) Run() error {
return sc.RunnerFunc()
}
func Test_myFuncThatUsesExecCmd(t *testing.T) {
// temporarily swap the shell commander
curShellCommander := shellCommander
defer func() { shellCommander = curShellCommander }()
shellCommander = func(name string, arg ...string) IShellCommand {
fmt.Printf("exec.Command() for %v called with %v and %v\n", t.Name(), name, arg)
return myShellCommand{
RunnerFunc: func() error {
return nil
},
}
}
// now that shellCommander is mocked, call the function that we want to test:
myFuncThatUsesExecCmd()
// do checks
}
答案4
得分: -3
如何模拟exec.Cmd / exec.Command()
?
你不能模拟exec.Cmd / exec.Command()
。请考虑使用非模拟的测试策略。
英文:
> How to mock *exec.Cmd / exec.Command()?
You cannot. Come up with a non mock-based testing strategy.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论