DRY 原则让测试整洁

1,115 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情

前言

进行单元测试的时候都需要造一大堆的用例,每次对一个方法的测试可能我会对每个用例都写一套测试逻辑。本着 DRY 的设计原理,准备做一个样板代码和用例配置,简化自己的单元测试测试过程。

单元测试

首先让我们看一下两个函数。

func addNum(num1, num2 int) int {
    return num1 + num2
}

func addString(str1, str2 string) string {
    return str1 + str2
}

一个函数接受 2 个整数并将它们相加,另外 2 个字符串。 让我们看一下测试第一个函数的方法。

func TestAddNum(t *testing.T) {
    t.Run("test1", func(t *testing.T) {
        value := addNum(2, 2)
        exp := 4
        if value != exp {
            t.Error("value %d, expected %d", value, exp)
        }
    })

    t.Run("test1", func(t *testing.T) {
        value := addNum(-1, -10)
        exp := -11
        if value != exp {
            t.Error("value %d, expected %d", value, exp)
        }
    })
    t.Run("test1", func(t *testing.T) {
        value := addNum(5, -10)
        exp := -5
        if value != exp {
            t.Error("value %d, expected %d", value, exp)
        }
    })
}

在这些测试中,使用了三组用例进行测试(正整数 + 正整数、负整数 + 正整数、负整数 + 负整数)。 从上面的代码上就可以看到有很多的重复的代码。 如果我们要添加更多的用例的时候,重复代码的数量随着每个新案例的增加而增加。

表驱动测试

最重要的干净编码原则之一是 DRY——不要重复自己,而经常被忽视的一点是你的测试也应该遵循正确的编码原则。

那么基于 DRY 的设计原理,要如何优化这个测试单元测试的代码呢?

相当简单的概念——我们将测试用例输入和预期答案存储在一个变量中并循环遍历每个用例——通常称为表驱动测试。

func TestAddNum(t *testing.T) {
    var tests = []struct {
        input []int
        ans   int
    }{
        {"test1", []int{10,10}, 20},
        {"test2", []string{-2,-5}, -7},
        {"test1", []string{8, -5}, 3},
    }

    for _, tt := range tests {
    t.Run("test", func(t *testing.T){
        value := addNum(tt.input[0], tt.input[1])
        exp := tt.ans
        if value != exp {
            t.Error("value %d, expected %d", value, exp)
        }
    }
  }
}

从上面代码中可以看到,先是声明“tests”是一个包含两个变量的结构切片:“inputs”是一个整数切片,我们将在其中存储测试用例输入,“ans”将在其中存储我们的预期答案。然后我们只需要遍历我们的测试变量并为每个条目运行一个测试。

下面是使用组合两个字符串的第二个函数的另一个示例。

func TestAddString(t *testing.T) {
    var tests = []struct {
        name  string
        input []string
        ans   string
    }{
        {"test1", []string{"hello", "world"}, "helloworld"},
        {"test2", []string{"sea", "food"}, "seafood"},
        {"test3", []string{"water", "melon"}, "watermelon"},
    }

    for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T){
        value := addString(tt.input[0], tt.input[1])
        exp := tt.ans
        if value != exp {
            t.Error("value %s, expected %s", value, exp)
        }
      }
    }
}

在上面的最终示例中,我们还在“测试”结构中添加了另一个参数 name,以便我们可以动态命名每个测试用例。

总结

在整个应用程序中遵循最佳编码原则,包括测试。 使用配置用例的方式不仅可以轻松增加任意的用例,还能让代码看起来简洁明了。