如何对用golang编写的hintui包进行单元测试?

时间:2018-11-14 18:14:20

标签: unit-testing go command-prompt

我是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,但无法找出在测试中执行此操作的最佳方法。

2 个答案:

答案 0 :(得分:2)

您不应尝试对promptui进行测试,因为它有望被其作者测试。

您可以测试的内容:

  1. 创建promptui.Prompt时发送正确的参数
  2. 您在代码中使用了promptui.Prompt
  3. 您正确处理了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
}