英文:
How to test nested input in Go
问题
我有一个使用os.Stdin
获取用户输入的函数。
func (i input) GetInput(stdin io.Reader) (string, error) {
reader := bufio.NewReader(stdin)
data, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("获取输入错误:%w", err)
}
return strings.ReplaceAll(data, "\n", ""), nil
}
在我的程序中,我需要有两个输入:
- 第一个用于获取基本信息,例如用户名
- 第二个用于获取依赖于第一个输入的附加信息
name, err := GetInput(os.Stdin)
if err != nil {
// 错误处理...
}
switch name {
case "test":
// 做一些事情...
age, err := GetInput(os.Stdin)
if err != nil {
// 错误处理...
}
fmt.Println(age)
case "another":
// 这里是另一个输入
}
对于这种情况,是否可以编写单元测试?
对于测试一个用户输入,我使用以下代码片段,它可以正常工作:
var stdin bytes.Buffer
stdin.Write([]byte(fmt.Sprintf("%s\n", tt.input)))
GetInput(stdin)
但是对于嵌套的两个输入,它不起作用。
英文:
I have function for getting user input using os.Stdin
func (i input) GetInput(stdin io.Reader) (string, error) {
reader := bufio.NewReader(stdin)
data, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("get input error: %w", err)
}
return strings.ReplaceAll(data, "\n", ""), nil
}
In my programm I need to have 2 inputs:
- First for getting base info for example user name
- Second for getting aditional info that depends on first input
name,err := GetInput(os.Stdin)
if err != nil {
// error handling.....
}
switch name {
case "test":
//do something...
age, err := GetInput(os.Stdin)
if err != nil {
// error handling.....
}
fmt.Println(age)
case "another":
// Here another input
}
It it possible to write unit tests for that case?
For testing one user input I use this snippet and it works:
var stdin bytes.Buffer
stdin.Write([]byte(fmt.Sprintf("%s\n", tt.input)))
GetInput(stdin)
But it didn't work with 2 nested inputs
答案1
得分: 1
也许考虑创建一个返回特定类型作为结果并将其放入单独的包中的函数。
由于我看到提到了name
和age
,也许我们可以假设一个具体的类型,比如Person
来进行说明。
重要的是要注意,我们希望将实际的读取器作为参数包含进来,而不是硬编码引用os.Stdin
。这样首先就可以模拟嵌套输入。
有了这个,方法的签名可能如下所示:
func NestedInput(input io.Reader) (*Person, error)
相应的类型可以是:
type Person struct {
Name string
Age int
}
如果现在将你的代码片段组合成一个名为input.go
的完整GO文件,并放在一个单独的目录中,它可能如下所示:
package input
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)
func getInput(reader *bufio.Reader) (string, error) {
data, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("获取输入错误:%w", err)
}
return strings.ReplaceAll(data, "\n", ""), nil
}
type Person struct {
Name string
Age int
}
func NestedInput(input io.Reader) (*Person, error) {
reader := bufio.NewReader(input)
name, err := getInput(reader)
if err != nil {
return nil, err
}
switch name {
case "q":
return nil, nil
default:
ageStr, err := getInput(reader)
if err != nil {
return nil, err
}
age, err := strconv.Atoi(ageStr)
if err != nil {
return nil, err
}
return &Person{Name: name, Age: age}, nil
}
}
输入q
将返回nil, nil
,可以用来终止输入,例如在循环中进行查询。
单元测试
现在,名为input_test.go
的文件中的单元测试
func Test_nestedInput(t *testing.T)
应该提供输入数据。
由于NestedInput
函数现在期望一个io.Reader
作为参数,我们可以使用以下方式简单生成所需的输入:
input := strings.NewReader("George\n26\n")
因此,测试可能如下所示:
package input
import (
"strings"
"testing"
)
func Test_nestedInput(t *testing.T) {
input := strings.NewReader("George\n26\n")
person, err := NestedInput(input)
if err != nil {
t.Error("嵌套输入失败")
}
if person == nil {
t.Errorf("期望得到person,但得到了nil")
return
}
if person.Name != "George" {
t.Errorf("错误的姓名%s,期望'George'", person.Name)
}
if person.Age != 26 {
t.Errorf("错误的年龄%d,期望26", person.Age)
}
}
当然,测试可以根据需要进行扩展。但是,正如你所看到的,这样模拟了一个嵌套输入。
英文:
Maybe consider having a function that returns a specific type as a result and put it into a separate package.
Since I see name
and age
mentioned, perhaps we can assume a concrete type like Person
for illustration.
It is important to note that we want to include the actual reader as a parameter and not have a hard coded reference to os.Stdin
. This makes the mocking of nested inputs possible in the first place.
With this, the signature of the method could look something like the following:
func NestedInput(input io.Reader) (*Person, error)
The corresponding type could be:
type Person struct {
Name string
Age int
}
If one now combines your code snippets to a complete GO file with the name input.go
in a separate directory, it might look something like this:
package input
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)
func getInput(reader *bufio.Reader) (string, error) {
data, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("get input error: %w", err)
}
return strings.ReplaceAll(data, "\n", ""), nil
}
type Person struct {
Name string
Age int
}
func NestedInput(input io.Reader) (*Person, error) {
reader := bufio.NewReader(input)
name, err := getInput(reader)
if err != nil {
return nil, err
}
switch name {
case "q":
return nil, nil
default:
ageStr, err := getInput(reader)
if err != nil {
return nil, err
}
age, err := strconv.Atoi(ageStr)
if err != nil {
return nil, err
}
return &Person{Name: name, Age: age}, nil
}
}
An input of q
returns nil, nil
and could be used to terminate the input, e.g. if the query was made in a loop.
Unit Test
The unit test
func Test_nestedInput(t *testing.T)
in a file named input_test.go
should now provide the input data.
Since the NestedInput
function now expects an io.Reader
as a parameter, we can simply generate the desired input with, for example,
input := strings.NewReader("George\n26\n")
So the test could look something like this:
package input
import (
"strings"
"testing"
)
func Test_nestedInput(t *testing.T) {
input := strings.NewReader("George\n26\n")
person, err := NestedInput(input)
if err != nil {
t.Error("nested input failed")
}
if person == nil {
t.Errorf("expected person, but got nil")
return
}
if person.Name != "George" {
t.Errorf("wrong name %s, expected 'George'", person.Name)
}
if person.Age != 26 {
t.Errorf("wrong age %d, expected 26", person.Age)
}
}
Of course, the tests can be extended with further details. But this, as you can see, mocks a nested input.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论