我是golang的新手,我在我的一个项目中使用了一个名为hinterui(https://github.com/manifoldco/promptui)的交互式提示。我已经为该项目编写了多个单元测试,但是我在如何对该需要输入的特定程序包进行单元测试中苦苦挣扎。
例如,我将如何测试以下代码行(封装在函数中):
func setEmail() string {
prompt := promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
email, err := prompt.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}
我认为我需要以某种方式模拟stdin,但无法找出在测试中执行此操作的最佳方法。
答案 0 :(得分:2)
您不应尝试对promptui
进行测试,因为它有望被其作者测试。
您可以测试的内容:
promptui.Prompt
时发送正确的参数promptui.Prompt
promptui.Prompt
个结果如您所见,所有这些测试都无法验证promptui.Prompt
是否在内部正常工作。
测试#2和#3可以组合使用。您需要针对模拟运行代码,如果得到正确的结果,则可以认为#2和#3都是正确的。
创建模拟:
type Runner interface {
Run() (int, string, error)
}
type promptMock struct {
// t is not required for this test, but it is would be helpful to assert input parameters if we have it in Run()
t *testing.T
}
func (p promptMock) Run() (int, string, error) {
// return expected result
return 1, "", nil
}
您将需要单独的模拟来测试错误流。
更新您的代码以注入模拟:
func setEmail(runner Runner) string {
email, err := runner.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}
现在可以测试了。
创建可创建prompt
的函数:
func getRunner() promptui.Prompt {
return promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
}
编写简单的断言测试以验证我们是否创建了正确的结构。
唯一未经测试的行将是setEmail(getRunner())
,但它是微不足道的,并且可以被其他类型的测试覆盖。
答案 1 :(得分:1)
无论出于何种原因,他们都不会导出其stdin
接口(https://github.com/manifoldco/promptui/blob/master/prompt.go#L49),因此您无法对其进行模拟,但是您可以直接模拟os.Stdin
并预先填充无论您需要测试什么。尽管我同意@Adrian的观点,但它具有自己的测试,因此没有必要。
从以下来源提取和重构/简化:Fill os.Stdin for function that reads from it
以这种方式重构,它可用于从os.Stdin
读取并需要特定字符串的任何函数。
游乐场链接:https://play.golang.org/p/rjgcGIaftBK
func TestSetEmail(t *testing.T) {
if err := TestExpectedStdinFunc("email@test.com", setEmail); err != nil {
t.Error(err)
return
}
fmt.Println("success")
}
func TestExpectedStdinFunc(expected string, f func() string) error {
content := []byte(expected)
tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
return err
}
defer os.Remove(tmpfile.Name()) // clean up
if _, err := tmpfile.Write(content); err != nil {
return err
}
if _, err := tmpfile.Seek(0, 0); err != nil {
return err
}
oldStdin := os.Stdin
defer func() { os.Stdin = oldStdin }() // Restore original Stdin
os.Stdin = tmpfile
actual := f()
if actual != expected {
return errors.New(fmt.Sprintf("test failed, exptected: %s actual: %s", expected, actual))
}
if err := tmpfile.Close(); err != nil {
return err
}
return nil
}