在今天的文章中,我们将研究如何在Go中编写测试。测试驱动开发,也被称为TDD,是一种鼓励开发者在开发过程中测试其代码的范式。
什么是测试,我为什么要关心?
测试是为了验证你的代码的行为是否符合要求而编写的一段代码。起初,这可能听起来很乏味。
哼。我对我的代码了如指掌。我知道我在做什么。测试是如此的浪费时间。
- 这是你的遗言:')
我曾经认为写测试也是一种浪费时间的行为。我写了代码,所以我应该最清楚它是否工作,对吗?我总是可以直接打印调试并修复错误,对吗?当我的函数从别的地方被调用时,不可能出现错误,对吗?...对吗?
有几个原因可以说明编写测试的重要性。当你通过你的职业生涯或你的学校项目取得进展时,你有可能会与其他人一起工作。当你们一起做一个项目时,你们可能会负责编写一段必须由其他人使用的代码。我们称其为库。当其他人依赖你的代码正常工作时,确保你处理好边缘情况是至关重要的,否则他们需要等待,直到你修复它。这可能是非常耗时的,而且大多可以通过在路上测试你的代码来防止。测试就像你在独自编码时不知道自己需要的工具,但在团队项目中却恰好是救命稻草。
编写测试的第二个原因是,它通常会导致更清洁的代码。当我们提到测试时,我们通常指的是单元测试,这意味着我们正在测试代码的每个单独组件。为了编写这些测试,你需要以一种模块化的方式编写你的代码。例如,不要用一个巨大的、臃肿的函数,我们可以把它拆成原子函数,做好一件事。然后,这些函数中的每一个都可以被单独测试,以确保它们正常工作。
第三个原因是比较主观的。我认为TDD可以帮助我更好地将事情可视化。在TDD中,我们鼓励你在实际实现功能之前先写测试。在我的例子中,通过先写测试,我更确定我的模块应该接受什么作为输入,应该吐出什么作为输出。我也可以事先想到边缘案例,这样我就不需要在事后去修正我的实现。
用Go写测试
在Go中编写测试是非常容易的。Go在其标准库中包含了测试包,我认为这是该语言开发者的一个非常周到的决定。你并不像其他语言那样需要一个单独的测试框架或库,这一点我很欣赏。
Go测试存储在*_test.go文件中。星号代表你的.go文件,里面有你想测试的函数。例如,假设我有一个 areas.go 文件,其中包含计算不同形状面积的函数。这些函数的测试将在 areas_test.go 中进行。
让我们先来看看我们的代码的高级概述。正如我之前提到的,areas.go将存储三个计算面积的函数:findTriangleArea、findSquareArea和findCircleArea。我们将在后面写出实现方法。我们应该先写输入和输出,否则Go会在我们写测试的时候抱怨。
package areas
func findTriangleArea(base, height float64) float64 {
func findSquareArea()
func findCircleArea()
现在我们可以开始写我们的测试了。记住,我们要在函数之前写测试。创建一个名为 areas_test.go 的文件并输入以下内容。
package areas
import (
我们从导入这些包开始。
func TestFindTriangleArea(t *testing.T) {
if actualArea != expectedArea {
这就是findTriangleArea的测试函数。所有Go测试函数的名称都是TestXxx(t *testing.T)的格式,其中Xxx是你要测试的函数的名称。你的测试的第一个字符必须大写。testing.T是一个变量,负责跟踪你的测试的状态。T可以用来调用特定的方法,如Errorf或Fatalf来终止测试,返回错误,记录结果,等等。
在函数内部,我们看到测试的主要逻辑。简单地说,一个测试会检查预期值和实际值是否匹配。在我们的例子中,我们调用findTriangleArea,基数为2.0,高度为3.0。我们期望看到3.0的输出,因为三角形的面积是由(底*高)/2计算出来的。如果调用findTriangleArea返回一个非零的错误,或者如果返回的面积与预期的面积不一致,我们将输出一个错误并停止测试。
t.Fatalf就像fmt.Sprintf一样,我们可以根据自己的喜好来格式化输出。我喜欢输出预期值,我们得到的实际值,以及任何错误(如果有)。
让我们来运行测试。要在Go中运行一个测试,我们到终端去,然后cd到测试文件所在的目录。然后,我们运行go test -v。你会得到像这样的输出。
$ go test -v
现在我们可以写出 findTriangleArea 的实现。
func findTriangleArea(base, height float64) float64 {
很简单,对吗?我也会为其他函数写一个。这是我们的最终结果。
// areas.go
import "math"
func findTriangleArea(base, height float64) float64 {
func findSquareArea(side float64) float64 {
func findCircleArea(radius float64) float64 {
// round to the 3rd decimal place
package areas
import (
func TestFindTriangleArea(t *testing.T) {
if actualArea != expectedArea {
func TestFindSquareArea(t *testing.T) {
if actualArea != expectedArea {
func TestFindCircleArea(t *testing.T) {
if actualArea != expectedArea {
如果我们现在运行go test -v,我们将得到如下结果。
$ go test -v
棒极了!我们的测试全部通过。
对多种情况的测试
这是所有的阳光和彩虹,但如果我们想测试多种情况呢?你可能有不止一个案例想进行测试。你是对的。
直观地说,我们可以直接写成这样。
func TestFindSquareArea(t *testing.T) {
if actualArea != expectedArea {
side = 3.0
if actualArea != expectedArea {
// repeat...
因为这段代码很快就会变得很难看,让我们试试别的东西。
func TestFindSquareArea(t *testing.T) {
findSquareAreaTests := []findSquareAreaTest{
for _, test := range findSquareAreaTests {
这样看起来好多了。我们声明了一个结构findSquareAreaTest,它持有边值和预期值。然后,我们创建了该结构的一个片断,它持有我们所有的测试案例。我们只需要使用for循环来迭代每个测试用例。这种类型的方法被称为表驱动的测试,因为我们使用的是一个容纳许多测试用例的表。
结语
谢谢你阅读这篇文章。我希望你觉得它很有用。当然,还有比我在这里列出的更高级的测试策略。我强烈建议你查看测试包 的文档 来深入了解。然而,这篇文章将让你开始进行测试驱动开发。尝试在你自己的个人项目中应用它吧下一次,我们将在更多有趣的话题中见面。再见!
用Go写测试》最初发表在《Dev Genius》杂志上,人们在那里通过强调和回应这个故事来继续对话。