Golang 中使用表驱动单元测试

我记得在我作为软件开发人员的早期,发现复制代码是一件很酷的事情。 并不是说我当时不知道 DRY 原则,而是当你尝试做一些事情且没有在意它时,代码重复就会习惯性地出现。 而且在大多数情况下,你并不关心那些让你不感兴趣的事情。

测试是初学者(或不负责任的人)程序员忽视的第一件事,而不是他/她书架上的数学书。 他/她试图完全避免测试,但出于某些原因(例如来自高级开发人员的压力),他/她会编写测试。 但是它包含类似的重复代码或遗漏了许多边缘情况。

那些关心我们所写内容的开发人员总是讨厌代码重复。 这就是为什么有一个称为 DRY 的合理原则。 这代表不要重复自己。 是的。 从现在开始,永远不要在这里和那里复制代码。 这对未来的维护来说是一件非常痛苦的事情。

我们都知道测试的重要性。 但是编写测试似乎很乏味。 所以,我们需要使用一些聪明的技巧来让编写测试变得有趣和容易。 有很多方法可以处理这个问题。 但我发现有两种方法很有效:

  1. 无论有多少测试用例,都尽可能快地进行测试
  2. 在编写测试用例的时候保持尽可能地简短和有趣

为了让测试立即运行,我们需要诊断是什么让我们的测试运行缓慢。 在大多数情况下,这是位于每个业务功能内部的 I/O 部分。 在 I/O 部分,我指的是诸如数据库调用、网络调用等。

但是我们需要这些东西,对吧? 它们同样重要。 这是我们需要使用抽象和依赖注入的地方。 这两件事将通过使用依赖注入在运行时提供抽象的模拟实现来确保我们的测试快速运行。 这是它自己的一个全新主题。 我会写一篇关于它的文章。

我们来看一下第二部分。

如果您是一名 Go 开发人员,您应该会发现以下代码很熟悉:

package reversestring

import (
   "strings"
)

func ReverseString(s string) string {
   if s == "" {
      return ""
   }

   var newString []string
   for i := len(s)-1; i >= 0; i-- {
      newString = append(newString, string(s[i]))
   }
   return strings.Join(newString, "")
}

这是一个反转字符串的简单函数。 现在告诉我您如何测试这个?

您从检查空字符串结果开始:

package reversestring

import (
   "testing"
)

func TestReverseString(t *testing.T) {
   if ReverseString("") != "" {
      t.Fail()
   }
}

然后您运行:

go test -v

然后您编写另一个测试用例:

package reversestring

import (
   "testing"
)

func TestReverseString(t *testing.T) {
   if ReverseString("") != "" {
      t.Fail()
   }

   if ReverseString("hello") != "olleh" {
      t.Fail()
   }
}

然后再次运行测试。 然后再添加一个,运行测试……循环继续。

砰!

感觉好无聊对吧? 编写这样的测试用例并不令人兴奋。
我们都知道 Go 是一门令人兴奋和有趣的语言。 因此在 Go 中应该有令人兴奋和有趣的方式编写单元测试。 让我们看看它是什么。
另外,你能在这里看到代码重复吗? 这里只有字符串部分发生了变化,其他保持不变。 但是我们需要为每个新的字符串测试用例编写一个新的 If...else 条件。

这不是一个有效的方法。 对吧?

这就是表驱动测试派上用场的地方。 让我们看看它是什么样子:

package reversestring

import (
   "testing"
)

func TestReverseString1(t *testing.T) {
   type args struct {
      s string
   }
   tests := []struct {
      name string
      args args
      want string
   }{
      // write your test case here....
   }
   for _, tt := range tests {
      t.Run(tt.name, func(t *testing.T) {
         if got := ReverseString(tt.args.s); got != tt.want {
            t.Errorf("ReverseString() = %v, want %v", got, tt.want)
         }
      })
   }
}

这是表驱动测试的样板设置代码。 它为你设置了一切。 你只需要为函数提供输入以及它的预期输出。 其余的事情,如断言、检查错误等,将由我们精心编写的表格驱动测试来处理。

如果你遵循有关表驱动测试的官方 go 指南(可以在此处找到),那么此代码对你来说可能有点不同。

这是官方版本的略微修改版本。 我使用很棒的 gotests 包生成了它。 它支持主要的文本编辑器和 IDE。 请检查一下。

好的,再次回到代码。 有趣的是,我们现在可以以声明方式而不是命令方式编写测试。 我们只是告诉 「 我想为函数提供这个和这个输入,我想检查输出是否是这个 」 ,这将为你完成。 令人兴奋,对吧?

让我们看看它的运行:

package reversestring

import (
   "testing"
)

func TestReverseString1(t *testing.T) {
   type args struct {
      s string
   }
   tests := []struct {
      name string
      args args
      want string
   }{
      {
         name: "testing empty",
         args: args{
            s:"",
         },
         want: "",
      },
      {
         name: "testing hello",
         args: args{
            s:"hello",
         },
         want: "olleh",
      },
      {
         name: "testing question",
         args: args{
            s:"why am I?",
         },
         want: "?I ma yhw",
      },
   }
   for _, tt := range tests {
      t.Run(tt.name, func(t *testing.T) {
         if got := ReverseString(tt.args.s); got != tt.want {
            t.Errorf("ReverseString() = %v, want %v", got, tt.want)
         }
      })
   }
}

这里,我通过给它一个名字,传递输入参数和我想要的输出来编写测试用例。 这就是全部。 现在看起来如此自然和有趣。 我们不需要为每个测试用例编写一个全新的函数或 if...else 用例。

我们现在可以运行测试:

go test -v

输出是:

=== RUN TestReverseString1
=== RUN TestReverseString1/testing_empty
=== RUN TestReverseString1/testing_hello
=== RUN TestReverseString1/testing_question
 — — PASS: TestReverseString1 (0.00s)
 — — PASS: TestReverseString1/testing_empty (0.00s)
 — — PASS: TestReverseString1/testing_hello (0.00s)
 — — PASS: TestReverseString1/testing_question (0.00s)
PASS
ok reversestring 0.003s

太棒了。 现在你可以使用一个名字、输入参数和预期输出添加新的测试用例:

{
   name: "testing fruit name",
   args: args{
      s:"mango",
   },
   want: "ognam",
},

所以现在,编写单元测试将减轻你的负担。 你不再需要编写重复的代码。

我认为在不再次使用表驱动测试的情况下你将不再编写测试用例。
所以,这就是全部。 我在本文末尾列出了一些阅读清单。 去看一下。

github.com/golang/go/wiki/TableDri...
dave.cheney.net/2019/05/07/prefer-...
dave.cheney.net/2013/06/09/writing...
ieftimov.com/post/testing-in-go-ta...
medium.com/@pliutau/table-driven-t...
medium.com/@cep21/closure-driven-t...
yourbasic.org/golang/table-driven-...
jayson.dev/blog/2019/06/table-driv...
dzone.com/articles/table-driven-te...

感觉很无聊,对吧? 编写这样的测试用例并不令人兴奋。
我们都知道 Go 是

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://www.codementor.io/@cyantarek15/h...

译文地址:https://learnku.com/go/t/61768

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!