Go语言:利用 TDD 测试驱动开发帮助理解数组与动态数组(切片)的区别
Array VS Slice
Sum 函数,它使用 for 来循环获取数组中的元素并返回所有元素的总和。先写测试(array)
sum_test.go 中:
package main
import "testing"
func TestSum(t *testing.T) {
numbers := [5]int{1, 2, 3, 4, 5}
got := Sum(numbers)
want := 15
if want != got {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
}
数组的容量是我们在声明它时指定的固定值。我们可以通过两种方式初始化数组:

关于其他引用类型的介绍参考
在错误信息中打印函数的输入有时很有用。
我们使用 %v(默认输出格式)占位符来打印输入,它非常适用于展示数组。
运行测试
我们创建一个Sum.go文件,不着急补全,我们先写个框架,让它编译通过
package main
func Sum(numbers [5]int) (sum int) {
return 0
}
这时测试还会失败,不过会返回明确的错误信息:
sum_test.go:13: got 0 want 15 given, [1 2 3 4 5]
这个时候把代码补充完整,使得它能够通过测试:
func Sum(numbers [5]int) int {
sum := 0
for i := 0; i < 5; i++ {
sum += numbers[i]
}
return sum
}
可以使用 取下标 也就是 array[index] 语法来获取数组中指定索引对应的值。
在本例中我们使用 for 循环分 5 次取出数组中的元素并与 sum 变量求和。
重构一(改进代码)
我们可以使用 range 语法来让函数变得更加整洁。
func Sum(numbers [5]int) int {
sum := 0
for _, number := range numbers {
sum += number
}
return sum
}
range 会迭代数组,每次迭代都会返回数组元素的索引和值。我们选择使用 '_' 空白标志符 来忽略索引。
数组和它的类型
[4]int 作为 [5]int 类型的参数传入函数,是不能通过编译的。string 当做 int 类型的参数传入函数一样。Sum先写测试(slice)
我们会使用 切片类型,它可以接收不同大小的切片集合。语法上和数组非常相似,只是在声明的时候不指定长度:

func TestSum(t *testing.T) {
t.Run("collection of 5 numbers", func(t *testing.T) {
numbers := [5]int{1, 2, 3, 4, 5}
got := Sum(numbers)
want := 15
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
t.Run("collection of any size", func(t *testing.T) {
numbers := []int{1, 2, 3}
got := Sum(numbers)
want := 6
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
}
运行测试
当然会编译出错:

同样先使用最少的代码来让失败的测试先跑起来
- 修改现有的 API,将
Sum函数的参数从数组改为切片。如果这么做我们就有可能会影响使用这个 API 的人,因为我们的 其他 测试不能编译通过。
- 创建一个新函数。
根据目前的情况,并没有人使用我们的函数,所以选择修改原来的函数。
func Sum(numbers []int) int {
sum := 0
for _, number := range numbers {
sum += number
}
return sum
}
如果你运行测试,它们还是不能编译通过,你必须把之前测试代码中的数组换成切片

再把 Sum 补充完整,使得它能够通过测试:
事实证明,这里需要我们做的只是修复编译器错误,然后测试就通过了。
重构二
我们已经重构了 Sum 函数把参数从数组改为切片。注意不要在重构以后忘记维护你的测试代码。
func TestSum(t *testing.T) {
t.Run("collection of 5 numbers", func(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5}
got := Sum(numbers)
want := 15
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
t.Run("collection of any size", func(t *testing.T) {
numbers := []int{1, 2, 3}
got := Sum(numbers)
want := 6
if got != want {
t.Errorf("got %d want %d given, %v", got, want, numbers)
}
})
}
go test -cover
你会看到:

现在删除一个测试,然后再次运行。
新的想法
这回我们需要一个 SumAll 函数,它接受多个切片,并返回由每个切片元素的总和组成的新切片。

先写测试
func TestSumAll(t *testing.T) {
got := SumAll([]int{1,2}, []int{0,9})
want := []int{3, 9}
if got != want {
t.Errorf("got %v want %v", got, want)
}
}
运行测试

先使用最少的代码来让失败的测试先跑起来
SumAll。func SumAll(numbersToSum ...[]int) (sums []int) {
return
}
这时运行测试会报编译时错误:
./sum_test.go:26:9: invalid operation: got != want (slice can only be compared to nil)
在 Go 中不能对切片使用等号运算符。你可以写一个函数迭代每个元素来检查它们的值。
但是一种比较简单的办法是使用 reflect.DeepEqual,它在判断两个变量是否相等时十分有用。
func TestSumAll(t *testing.T) {
got := SumAll([]int{1,2}, []int{0,9})
want := []int{3, 9}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
确保你已经在文件头部 import reflect,这样你才能使用 DeepEqual 方法。
需要注意的是 reflect.DeepEqual 不是「类型安全」的,所以有时候会发生比较怪异的行为。
为了看到这种行为,暂时将测试修改为:
func TestSumAll(t *testing.T) {
got := SumAll([]int{1,2}, []int{0,9})
want := "bob"
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
slice 和 string。这显然是不合理的,但是却通过了编译!所以使用 reflect.DeepEqual 比较简洁但是在使用时需多加小心。Sum 计算每个参数的总和并把结果放入函数返回的切片中。
func SumAll(numbersToSum ...[]int) (sums []int) {
lengthOfNumbers := len(numbersToSum)
sums = make([]int, lengthOfNumbers)
for i, numbers := range numbersToSum {
sums[i] = Sum(numbers)
}
return
}
make 可以在创建切片的时候指定我们需要的长度和容量。= 对切片元素进行赋值。
重构三
mySlice[10]=1 进行赋值,会报运行时错误。append 函数,它能为切片追加一个新值。
func SumAll(numbersToSum ...[]int) []int {
var sums []int
for _, numbers := range numbersToSum {
sums = append(sums, Sum(numbers))
}
return sums
}
SumAll 变成 SumAllTails还有一个新想法
先写测试
func TestSumAllTails(t *testing.T) {
got := SumAllTails([]int{1,2}, []int{0,9})
want := []int{2, 9}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
运行测试
./sum_test.go:26:9: undefined: SumAllTails
先使用最少的代码来让失败的测试先跑起来
把函数名称改为 SumAllTails 并重新运行测试
sum_test.go:30: got [3 9] want [2 9]
将代码补充完整使函数能够测试通过
func SumAllTails(numbersToSum ...[]int) []int {
var sums []int
for _, numbers := range numbersToSum {
tail := numbers[1:]
sums = append(sums, Sum(tail))
}
return sums
}
我们可以使用语法 slice[low:high] 获取部分切片。如果在冒号的一侧没有数字就会一直取到最边缘的元素。
在我们的函数中,我们使用 numbers[1:] 取到从索引 1 到最后一个元素。
重构四
myEmptySlice[1:]会发生什么?先写测试
func TestSumAllTails(t *testing.T) {
t.Run("make the sums of some slices", func(t *testing.T) {
got := SumAllTails([]int{1,2}, []int{0,9})
want := []int{2, 9}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
})
t.Run("safely sum empty slices", func(t *testing.T) {
got := SumAllTails([]int{}, []int{3, 4, 5})
want :=[]int{0, 9}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
})
}
运行测试
会报 panic
func SumAllTails(numbersToSum ...[]int) []int {
var sums []int
for _, numbers := range numbersToSum {
if len(numbers) == 0 {
sums = append(sums, 0)
} else {
tail := numbers[1:]
sums = append(sums, Sum(tail))
}
}
return sums
}
重构五
我们的测试代码有一部分是重复的,我们可以把它放到另一个函数中复用。
func TestSumAllTails(t *testing.T) {
checkSums := func(t *testing.T, got, want []int) {
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
t.Run("make the sums of tails of", func(t *testing.T) {
got := SumAllTails([]int{1, 2}, []int{0, 9})
want := []int{2, 9}
checkSums(t, got, want)
})
t.Run("safely sum empty slices", func(t *testing.T) {
got := SumAllTails([]int{}, []int{3, 4, 5})
want := []int{0, 9}
checkSums(t, got, want)
})
}
这样使用起来更加方便,而且还能增加代码的类型安全性。如果一个粗心的开发者使用 checkSums(t, got, "dave") 是不能通过编译的
总结
- 数组
- 切片
- 多种方式的切片初始化
- 切片的容量是 固定 的,但是你可以使用
append从原来的切片中创建一个新切片 - 如何获取部分切片
- 使用
len获取数组和切片的长度
- 使用测试代码覆盖率的工具
reflect.DeepEqual的妙用和对代码类型安全性的影响
[][]stringGo语言:利用 TDD 测试驱动开发帮助理解数组与动态数组(切片)的区别的更多相关文章
- TDD(测试驱动开发)学习一:初识TDD
首先说一下名词解释,TDD,英文名称Test-Driven Development,中文名称测试驱动开发,简单的断下句“测试/驱动/开发”,简单的理解一下,就是测试驱动着开发,大白话就是说用一边测试一 ...
- TDD(测试驱动开发)培训录
2014年我一直从事在敏捷实践咨询项目,这也是我颇有收获的一年,特别是咨询项目的每一点改变,不管是代码质量的提高,还是自组织团队的建设,都能让我们感到欣慰.涉及人的问题都是复杂问题,改变人,改变一个组 ...
- TDD(测试驱动开发)培训录(转)
本文转载自:http://www.cnblogs.com/whitewolf/p/4205761.html 最近也在了解TDD,发现这篇文章不错,特此转载一下. TDD(测试驱动开发)培训录 2015 ...
- TDD(测试驱动开发)
TDD(测试驱动开发)培训录 2014年我一直从事在敏捷实践咨询项目,这也是我颇有收获的一年,特别是咨询项目的每一点改变,不管是代码质量的提高,还是自组织团队的建设,都能让我们感到欣慰.涉及人的问题都 ...
- TDD(测试驱动开发)学习二:创建第一个TDD程序
本节我们将学习一些测试驱动开发环境的搭建,测试驱动开发概念和流程.所涉及的内容全部会以截图的形式贴出来,如果你也感兴趣,可以一步一步的跟着来做,如果你有任何问题,可以进行留言,我也会很高兴的为你答疑. ...
- (译)TDD(测试驱动开发)的5个步骤
原文:5 steps of test-driven development https://developer.ibm.com/articles/5-steps-of-test-driven-deve ...
- C语言数组:C语言数组定义、二维数组、动态数组、字符串数组
1.C语言数组的概念 在<更加优美的C语言输出>一节中我们举了一个例子,是输出一个 4×4 的整数矩阵,代码如下: #include <stdio.h> #include &l ...
- C语言柔性数组和动态数组
[前言]经常看到C语言里的两个数组,总结一下. 一.柔性数组 参考:https://www.cnblogs.com/veis/p/7073076.html #include<stdio.h> ...
- 基于SOA架构的TDD测试驱动开发模式
以需求用例为基,Case&Coding两条线并行,服务(M)&消费(VC)分离,单元.接口.功能.集成四层质量管理,自动化集成.测试.交付全程支持. 3个大阶段(需求分析阶段.研发准备 ...
- TDD(测试驱动开发)的推广方法论
随机推荐
- 5G智慧灯杆系统在智慧街区的应用
智慧化的路灯作为一个高度集成的项目,是智慧城市在城市公共空间的落地载体,是一个自上而下的体系,有外延.可扩展.能适配智慧城市的建设要求.在商业街开展智慧灯杆建设,同期开展5G应用技术试点,有利于商业街 ...
- lua 添加的时候去重
result = {} ids = {1,9,6,7}affs = {3,2,4,5,6}count =0for s in *ids result[s]=sfor p, v in pairs resu ...
- pycharm开发工具的介绍和使用
pycharm开发工具的介绍和使用 PyCharm是常用的python开发工具之一,分为社区版和专业版,社区版只有基础的python环境,专业版的功能会多很多
- 作业三:CART回归树算法
作业三:CART回归树算法 班级:20大数据(3)班 学号:201613341 题目一 表1为拖欠贷款人员训练样本数据集,使用CART算法基于该表数据构造决策树模型,并使用表2中测试样本集确定剪枝后的 ...
- gitt如何将本地分支同远程分支进行关联
将本地分支同远程分支进行关联,1.本地已经创建了分支test(test,是master以外自己创建的分支),而远程没有2种方法在远程创建分支test,并与本地分支进行关联: 方法1: git push ...
- guava缓存
Guava Cache有一些优点如下 :1. 线程安全的缓存, 与ConcurrentMap相似(前者更"好"), 在高并发情况下.能够正常缓存更新以及返回.2. 提供了三种基本的 ...
- Ubuntu16python3.5升级3.6apt-getupdate遇到403forbidden
查了好多发现都不顶用 其实是因为jonathof的源停止对外开源了 真正解决问题的博客
- Linux_MySQL
MySQL 安装 AB复制 安装 1.编译安装 2.yum安装 [https://www.mysql.com/] yum安装的方式 1.在官网下载mysql rpm包 # wget https://d ...
- java从键盘输入数据
一.从键盘输入字符串 1.nex和nextLine的区别 next()读取到有效字符后才可以结束输入,对输入有效字符之前遇到的空格键.Enter键或Tab键等结束符,next()会自动将其去掉,只有在 ...
- gulp技术:自动化构建工具
作用:压缩css.js.img,合并文件,改名字,编译sass,拷贝 使用步骤: 1.安装node环境,下一步,下一步,安装C盘: 2.在你的根目录下,在地址栏输入cmd回车: 3.检测node和np ...