经过一段时间的学习与实践,针对 Golang 语言基础的 TDD 驱动开发测试训练已经告一段落,接下来会在此基础上继续使用 TDD 来构建应用程序 。
博主前一部分的代码Github先贴下面👇以供参考
https://github.com/slowlydance2me/My_Golang_Study.git
2023-03-29 今日更新
golang 挑战:编写函数
walk(x interface{}, fn func(string))
,参数为结构体x
,并对x
中的所有字符串字段调用fn
函数。难度级别:递归。
为此,我们需要使用 反射。
计算中的反射提供了程序检查自身结构体的能力,特别是通过类型,这是元编程的一种形式。这也是造成困惑的一个重要原因。
什么是 interface
?
string
,
int
以及我们自己定义的类型,如
BankAccount
,我们享受到了 Go 为我们提供的类型安全。
interface{}
来解决这个问题,你可以将其视为
任意 类型。
walk(x interface{}, fn func(string))
的
x 参数可以接收任何的值。
那么为什么不通过将所有参数都定义为 interface
类型来得到真正灵活的函数呢?
-
作为函数的使用者,使用
interface
将失去对类型安全的检查。如果你想传入string
类型的Foo.bar
但是传入的是int
类型的Foo.baz
,编译器将无法通知你这个错误。你也搞不清楚函数允许传递什么类型的参数。知道一个函数接收什么类型,例如UserService
,是非常有用的。
- 作为这样一个函数的作者,你必须检查传入的 所有 参数,并尝试断定参数类型以及如何处理它们。这是通过 反射 实现的。这种方式可能相当笨拙且难以阅读,而且一般性能比较差(因为程序必须在运行时执行检查)。
interface
类型,这里容易让人困惑)设计它,以便用户可以用多种类型来调用你的函数,这些类型实现了函数工作所需要的任何方法。
首先编写测试
我们想用一个 struct
来调用我们的函数,这个 struct
中有一个字符串字段(x
),然后我们可以监视传入的函数(fn
),看看它是否被调用。
func TestWalk(t *testing.T) {
expected := "Chris"
var got []string
x := struct {
Name string
}{expected}
walk(x, func(input string) {
got = append(got, input)
})
if len(got) != 1 {
t.Errorf("wrong number of function calls, got %d want %d", len(got), 1)
}
}
-
我们想存储一个字符串切片(
got
),字符串通过walk
传递到fn
。在前面的章节中,通常我们会专门为函数或方法调用指定类型,但在这种情况下,我们可以传递一个匿名函数给fn
,它会隐藏got
。
-
我们使用带有
string
类型的Name
字段的匿名struct
,以此得到最简单的实现路径。
- 最后调用
walk
并传入x
参数,现在只检查got
的长度,一旦有了基本的可以运行的程序,我们的断言就会更加具体。
尝试运行测试
./reflection_test.go:21:2: undefined: walk
为测试的运行编写最小量的代码,并检查测试的失败输出
我们需要定义 walk
函数
func walk(x interface{}, fn func(input string)) {
}
再次尝试运行测试
=== RUN TestWalk
--- FAIL: TestWalk (0.00s)
reflection_test.go:19: wrong number of function calls, got 0 want 1
FAIL
编写足够的代码使测试通过
我们可以使用任意的字符串调用 fn
函数来使测试通过。
func walk(x interface{}, fn func(input string)) {
fn("I still can't believe South Korea beat Germany 2-0 to put them last in their group")
}
现在测试应该通过了。接下来我们需要做的是对我们的 fn
是如何被调用的做一个更具体的断言。
首先编写测试
在之前的测试中添加以下代码,检查传入 fn
函数的字符串是否正确。
if got[0] != expected {
t.Errorf("got '%s', want '%s'", got[0], expected)
}
尝试运行测试
=== RUN TestWalk
--- FAIL: TestWalk (0.00s)
reflection_test.go:23: got 'I still can't believe South Korea beat Germany 2-0 to put them last in their group', want 'Chris'
FAIL
编写足够的代码使测试通过
func walk(x interface{}, fn func(input string)) {
val := reflect.ValueOf(x)
field := val.Field(0)
fn(field.String())
}
这段代码 非常不安全,也非常幼稚,但请记住,当我们处于「红色」状态(测试失败)时,我们的目标是编写尽可能少的代码。然后我们编写更多的测试来解决我们的问题。
x
并尝试查看它的属性。