繁体   English   中英

如何对这个用 golang 编写的 promptui package 进行单元测试?

[英]How do I unit test this promptui package written in golang?

我是 golang 的新手,我在我的一个项目中使用了一个名为 promptui ( https://github.com/manifoldco/promptui ) 的交互式提示。 我已经为这个项目编写了几个单元测试,但我正在努力解决如何对这个需要输入的特定 package 进行单元测试的问题。

例如,我 go 如何测试以下代码行(封装在一个函数中):

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 但无法在测试中找到最好的方法。

您不应该尝试测试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
}

您将需要单独的模拟来测试错误流。

更新你的代码以注入mock:

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())但它很简单,可以被其他类型的测试覆盖。

promptui 现在具有 Stdin 属性。

这里有一个小提琴: https://play.golang.org/p/-mSgjY2kAw-

这是我们将要测试的 function:

func mock(p promptui.Prompt) string {
    p.Label = "[Y/N]"
    user_input, err := p.Run()
    if err != nil {
        fmt.Printf("Prompt failed %v\n", err)
    }

    return user_input
}

我们需要创建p ,它将是 promptui.Prompt 的一个实例并具有自定义标准输入。

我在这里得到了一些帮助 - https://groups.google.com/g/golang-nuts/c/J-Y4LtdGNSw?pli=1 - 如何制作自定义 Stdin 值,它只需要符合 io.ReadCloser .

type ClosingBuffer struct {
    *bytes.Buffer
}

func (cb ClosingBuffer) Close() error {
    return nil
}

然后在阅读器中将其用作 Stdin:

func TestMock(t *testing.T) {

    reader := ClosingBuffer{
        bytes.NewBufferString("N\n"),
    }

    p := promptui.Prompt{
        Stdin: reader,
    }

    response := mock(p)

    if !strings.EqualFold(response, "N") {
        t.Errorf("nope!")
    }
    //t.Errorf(response)
}

编辑:以上内容不适用于同一个 function 中的多个提示,正如此处讨论的解决方案: https://github.com/manifoldco/promptui/issues/63 - “promptui 内部使用 4096 字节的缓冲区。这意味着您必须填充缓冲区,否则 promptui 将引发 EOF。”

我从那个交易所拿了这个pad() function - https://github.com/sandokandias/capiroto/blob/master/cmd/capiroto/main.go

func pad(siz int, buf *bytes.Buffer) {
    pu := make([]byte, 4096-siz)
    for i := 0; i < 4096-siz; i++ {
        pu[i] = 97
    }
    buf.Write(pu)
}

然后测试——这个解决方案使用 ioutil.NopCloser 而不是创建一个新的结构:

func TestMock(t *testing.T) {

    i1 := "N\n"
    i2 := "Y\n"

    b := bytes.NewBuffer([]byte(i1))
    pad(len(i1), b)
    reader := ioutil.NopCloser(
        b,
    )
    b.WriteString(i2)
    pad(len(i2), b)

    p := promptui.Prompt{
        Stdin: reader,
    }

    response := mock(p)

    if !strings.EqualFold(response, "NY") {
        t.Errorf("nope!")
        t.Errorf(response)
    }
}

和我们正在测试的 function:

func mock(p promptui.Prompt) string {
    p.Label = "[Y/N]"
    user_input, err := p.Run()
    if err != nil {
        fmt.Printf("Prompt failed %v\n", err)
    }
    user_input2, err := p.Run()

    return user_input + user_input2
}

多个提示的小提琴在这里: https://play.golang.org/p/ElPysYq8aM1

无论出于何种原因,他们不会导出他们的stdin界面( https://github.com/manifoldco/promptui/blob/master/prompt.go#L49 ),所以你不能嘲笑它,但你可以直接嘲笑os.Stdin并预先os.Stdin测试所需的一切。 虽然我同意@Adrian,但它有自己的测试,所以这不应该是必要的。

从源中提取和重构/简化: 填写os.Stdin以获取从中读取的函数

以这种方式重构,它可以用于从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
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM