【服务计算】二 《Learn Go with tests》“迭代”章节的练习和Go语言实现算法的TDD实践报告
- 操作系统
- 环境准备
- 概念定义与理解
-
- TDD的概念
- 重构的概念
- 测试的概念
- 基准测试的概念
- 通过TDD实践完成“迭代”章节的练习
-
- 先写测试
- 尝试运行测试
- 先使用最少的代码来让失败的测试先跑起来
- 把代码补充完整,使得它能够通过测试
- 重构
- 基准测试
- 修改测试代码,以便调用者可以指定字符重复的次数,然后修复代码
- 写一个 ExampleRepeat 来完善你的函数文档
- 看一下 strings 包。找到你认为可能有用的函数,并对它们编写一些测试。投入时间学习标准库会慢慢得到回报。
- 通过TDD实践完成欧几里得(辗转相除)算法
-
- 先写测试
- 尝试运行测试
- 先使用最少的代码来让失败的测试先跑起来
- 把代码补充完整,使得它能够通过测试
- 基准测试
- 重构
- 再次运行基准测试
- 写一个示例函数来完善函数文档
- 通过TDD实践完成快速排序算法
-
- 先写测试
- 先写测试
- 尝试运行测试
- 先使用最少的代码来让失败的测试先跑起来
- 把代码补充完整,使得它能够通过测试
- 基准测试
- 重构
- 再次运行基准测试
- 修改测试代码,以便调用者可以指定按顺序或逆序排序,然后修复代码
- 写一个示例函数来完善函数文档
- 小结
操作系统
按照实验要求,结合自己的电脑设备与系统等条件,使用VirtualBox下Ubuntu 20.04系统完成实验。虚拟机相关设置与上一次实验相同。
环境准备
虚拟机下的实验环境与上一次实验的相同,不需要额外的配置。
概念定义与理解
TDD的概念
TDD是测试驱动开发(Test-Driven Development)的缩写。
根据百度百科的定义:
TDD是测试驱动开发(Test-Driven Development)的英文简称,是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是敏捷方法的核心实践,但不只适用于XP(Extreme Programming),同样可以适用于其他开发方法和过程。
根据课程参考资料https://studygolang.gitbook.io中的指导,TDD过程的具体过程和特征如下:
- 编写一个失败的测试,并查看失败信息,我们知道现在有一个为需求编写的 相关 的测试,并且看到它产生了 易于理解的失败描述
- 编写最少量的代码使其通过,以获得可以运行的程序
- 然后 重构,基于我们测试的安全性,以确保我们拥有易于使用的精心编写的代码
通过基于TDD的软件开发,可以更好地保障软件质量,帮助更及时地发现功能模块潜在的问题并修复。基于TDD的软件开发也可以让我们更好地积累对已经完成的功能模块的信心,更好地聚焦于后续功能模块的开发。在产品发布时,经过了严格测试的功能模块也能让软件出现问题的可能性降低,减小发布团队的压力。
重构的概念
在软件开发领域,重构(Refactoring)就是通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。
一个软件总是为解决某种特定的需求而产生,时代在发展,客户的业务也在发生变化。有的需求相对稳定一些,有的需求变化的比较剧烈,还有的需求已经消失了,或者转化成了别的需求。在这种情况下,软件必须相应的改变。
软件在一次次面向客户的需求经过了相应的改变后,软件的结构可能发生了很大的变化。即使软件最初刚制造出来的时候是经过精心设计并具有良好结构的,但经过了一次次面向客户需求的修改,其可能面目全非且结构混乱,并且代码耦合度增高且难以维护。
为了解决这样的问题,就需要进行代码重构。使用重构的方式,不改变系统的外部功能,只对内部的结构进行重新的整理,使得软件的代码耦合度降低,更加紧凑和模块化,性能也可以得到提升,利于软件之后的维护和继续改进。
测试的概念
在软件开发领域,测试指的是为了保证软件的质量,对软件的功能进行测试,验证是否能满足设计需求。测试是一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。
通过周期性的对软件进行测试,可以评估软件是否达到设计需求,方便及时修正弥补软件的不足,并且保证了软件的质量有利于后续对软件项目的进一步开发。
基准测试的概念
根据百度百科:
基准测试是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。
基准测试常会调用被测试的代码/函数,通过运行若干次求平均值的方法,计算出这部分代码/函数的运行平均性能。通过基准测试,可以更好地了解软件性能和吞吐能力,更好地评估软件的运行效果。其也可以帮助在软件的多个方案之间进行比较,确定更好的解决方案。对于性能没有达到基准测试要求的软件,也可以根据其基准测试的结果进行有针对性的改进。
通过TDD实践完成“迭代”章节的练习
在 Go 中只能使用 for 来循环和迭代,下面根据教程的相关章节一步步实践练习Go的迭代语句。
Go中的for语句与C语言中的类似,但是for后面的初始化,循环条件等三条语句没有圆括号,且循环体外面必须要加大括号。
先写测试
这一节的工作目录在"$GOPATH/src/github.com/{your-user-id}/iteration"下
由于我的github的user id为alphabstc,这一节下面显示的工作目录就在"$GOPATH/src/github.com/alphabstc/iteration"之下。
首先为一个重复字符 5 次的函数编写测试。在上述工作目录下创建名为repeat_test.go的代码文件,在文件中输入下面的代码:
package iteration
import "testing"
func TestRepeat(t *testing.T) {
repeated := Repeat("a")
expected := "aaaaa"
if repeated != expected {
t.Errorf("expected '%q' but got '%q'", expected, repeated)
}
}
上面的测试检查Repeat(“a”)函数的返回值是否为"aaaaa"。
尝试运行测试
在工作目录下运行“go test”进行测试,运行结果如下:
可以看到,和教程预期的结果一样。由于目前包内还没有定义Repeat函数,测试的结果为没有找到Repeat函数的定义,运行失败。
先使用最少的代码来让失败的测试先跑起来
现在只需让代码可编译,这样就可以检查测试用例能否通过。
在包内,也就是之前所在的工作目录下,创建名为repeat.go的代码文件,并且在其中输入以下内容,完成package的声明和Repeat函数的定义。
package iteration
func Repeat(character string) string {
return ""
}
接着再次运行测试:
可以看到与预期结果相同。现在定义了Repeat函数,但没有具体实现其内容。因此现在可以通过编译并测试,由于还没有实现好Repeat函数的功能,因此测试结果是失败的。
把代码补充完整,使得它能够通过测试
接下来补充完整代码使得可以通过上面的测试。go语言也算是C语言族中的语言,for循环语句语法与C语言的也比较类似。
下面修改repeat.go中之前定义的Repeat函数内容如下:
func Repeat(character string) string {
var repeated string
for i := 0; i < 5; i++ {
repeated = repeated + character
}
return repeated
}
上面的函数使用for循环迭代5次,每次将character累加到字符串repeated上,使得repeated最后是5次重复的字符串character。
与 C,Java 或 JavaScript等其他C语言族中的语言不同, Go 中的 for循环 语句前导条件部分并没有圆括号,而且循环体外的大括号是必须的。
现在可以看到通过了测试,也符合预期。
重构
上面的代码通过了测试。现在重构一下代码,并检查是否仍然能通过测试。
现在将循环次数作为常量定义,并且在累加字符串的时候采用+=运算符而不是之前的+运算符。修改Repeat函数的代码如下:
const repeatCount = 5
func Repeat(character string) string {
var repeated string
for i := 0; i < repeatCount; i++ {
repeated += character
}
return repeated
}
可以看到,重构后的代码仍然可以通过测试,符合要求,说明了重构后代码的正确性。
基准测试
现在进一步在repeat_test.go中定义基准测试函数的代码如下:
func BenchmarkRepeat(b *testing.B) {
for i := 0; i < b.N; i++ {
Repeat("a")
}
}
可以看到,基准测试的函数定义和测试的是类似的。
基准测试运行时,Repeat会被调用 b.N 次,并测量需要多长时间。测试框架会选择一个它所认为的最佳值,以便获得更合理的结果。
以上结果说明在我的电脑上运行一次这个函数需要288纳秒。基准测试调用了它运行了3814573次。
修改测试代码,以便调用者可以指定字符重复的次数,然后修复代码
首先修改测试代码中的TestRepeat函数和BenchmarkRepeat函数,修改后的测试代码repeat_test.go调用Repeat会传入两个参数,一个为要被重复的字符串,一个是重复次数。修改后的代码如下:
func TestRepeat(t *testing.T) {
repeated := Repeat("a", 5)
expected := "aaaaa"
if repeated != expected {
t.Errorf("expected '%q' but got '%q'", expected, repeated)
}
}
func BenchmarkRepeat(b *testing.B) {
for i := 0; i < b.N; i++ {
Repeat("a", 5)
}
}
然后再次运行测试,会提示如下的错误:
上面的错误信息提示说,传入Repeat的参数过多。根据提示,现在修复Repeat的代码使其可以通过测试,修改后的代码如下:
func Repeat(character string, repeatCount int) string {
var repeated string
for i := 0; i < repeatCount; i++ {
repeated += character
}
return repeated
}
再次运行测试和基准测试:
可以看到,现在可以正常运行测试和基准测试。上面的结果说明通过了测试;且基准测试测量出运行一次Repeat函数平均需要264ns。为了测量出平均用时,调用了Repeat函数4320328次。
这样就实现了,可以指定重复次数的功能。
写一个 ExampleRepeat 来完善你的函数文档
Go 示例执行起来就像测试一样,示例反映出的是代码的实际功能。作为包的测试套件的一部分,示例会被编译(并可选择性地执行)。与典型的测试一样,示例是存在于一个包的 _test.go 文件中的函数。
因此,向repeat_test.go文件添加ExampleRepeat函数如下:
func ExampleRepeat(){
ans := Repeat("ab", 7)
fmt.Println(ans)
// Output: ababababababab
}
运行这个包的测试套件(-v选项会显示每个用例的测试结果),得到如下的结果:
可以看见,Repeat函数也通过了ExampleRepeat示例函数的测试。
之后验证文档中会包含以上的内容:
首先,在终端中输入并运行以下命令:
godoc -http=:6060
上面的命令运行了godoc服务端程序,并让其监听在6060端口。
之后打开浏览器,浏览http://localhost:6060/pkg/的网页内容如下:
可以看到,该网址包含很多包的说明文档。
之后找用户编写的iteration包的文档并点击查看:
可以看到文档中包括之前定义的Repeat函数的函数原型,以及示例函数ExampleRepeat的内容及运行结果。此时正确完成了ExampleRepeat函数并将其加入了示例文档。
看一下 strings 包。找到你认为可能有用的函数,并对它们编写一些测试。投入时间学习标准库会慢慢得到回报。
在上一小节的网址中进一步查看 strings 包,发现里面非常多关于字符串操作与处理的函数:
其中,strings中的index函数的原型和示例如下:
可以看到该函数可以求出一个字符串在另一个字符串中第一次出现的位置(不存在返回-1)。
为Index函数编写如下的测试:
func TestIndex(t *testing.T) {
result := strings.Index("abcdabcab", "dabc")
expected := 3
if result != expected {
t.Errorf("expected %d but got %d", expected, result)
}
}
之后,再进行测试:
可以看到通过了TestIndex函数的测试。
通过TDD实践完成欧几里得(辗转相除)算法
欧几里得(辗转相除)算法是求解两个数最大公约数的经典数论算法。其利用了如下的性质:
g c d ( a , b ) = g c d ( b , a m o d b ) gcd(a,b)=gcd(b, a \mod b) gcd(a,b)=gcd(b,amodb)
根据上面的性质,反复使用该等式,就可以最终求出两个数的gcd的值。
这一节的工作目录在"$GOPATH/src/github.com/{your-user-id}/numberTheory"下
由于我的github的user id为alphabstc,这一节下面显示的工作目录就在"$GOPATH/src/github.com/alphabstc/numberTheory"之下。
先写测试
先编写对最大公约数求解结果的测试。在上述工作目录下创建名为gcd_test.go的代码文件,在文件中输入下面的代码:
func TestGcd(t *testing.T) {
res := Gcd(42, 48)
expected := 6
if res != expected {
t.Errorf("expected %d but got %d", expected, res)
}
}
上面的代码检查了Gcd(42,48)函数的返回值是否为6。
尝试运行测试
在工作目录下运行“go test”进行测试,运行结果如下:
可以看到,由于目前包内还没有定义Gcd函数,测试的结果为没有找到Gcd函数的定义,运行失败。
先使用最少的代码来让失败的测试先跑起来
现在只需让代码可编译,这样就可以检查测试用例能否通过。
在包内,也就是之前所在的工作目录下,创建名为gcd.go的代码文件,并且在其中输入以下内容,完成package的声明和gcd函数的定义。
package numberTheory
func Gcd(a int, b int) int {
return 0
}
接着再次运行测试:
可以看到,现在定义了Gcd函数,但没有具体实现其内容。因此现在可以通过编译并测试,由于还没有实现好Gcd函数的功能,因此测试结果是失败的。
把代码补充完整,使得它能够通过测试
接下来补充完整代码使得可以通过上面的测试。go语言也算是C语言族中的语言,if条件分支语句语法与C语言的也比较类似。但与 C,Java 或 JavaScript等其他C语言族中的语言不同, Go 中的if条件分支语句前导条件部分并没有圆括号,而且大括号是必须的。并且关键字 if 和 else 之后的左大括号{必须和关键字在同一行,如果使用了 else if 结构,则前段代码块的右大括号 } 必须和 else if 关键字在同一行,这两条规则都是被编译器强制规定的。
下面修改gcd.go中之前定义的Gcd函数内容如下:
func Gcd(a int, b int) int {
if b == 0 {
return a
}
return Gcd(b, a % b)
}
修改后再次进行测试:
现在可以看到通过了测试,符合要求。
基准测试
现在进一步在gcd_test.go中定义基准测试函数的代码如下:
func BenchmarkGcd(b *testing.B) {
for i := 0; i < b.N; i++ {
Gcd(42, 48)
}
}
基准测试运行时,Gcd会被调用 b.N 次,并测量需要多长时间。测试框架会选择一个它所认为的最佳值,以便获得更合理的结果。
以上结果说明在我的电脑上运行一次这个函数平均需要42.4纳秒。基准测试调用了它运行了29394721次。
重构
上面的代码通过了测试。现在重构一下代码,并检查是否仍然能通过测试。
之前的Gcd函数代码是递归版的,现在修改其为非递归版的代码如下:
func Gcd(a int, b int) int {
var r int
for ; b != 0; {
r = a % b
a = b
b = r
}
return a
}
可以看到,重构后的代码仍然可以通过测试,符合要求,说明了重构后代码的正确性。
再次运行基准测试
以上结果说明在我的电脑上运行一次这个函数平均需要31.6纳秒。基准测试调用了它运行了35361135次。可见,采用非递归版本的欧几里得算法代码运行速度比递归版代码更快,在上面的基准测试中平均用时减少了大约四分之一。
写一个示例函数来完善函数文档
继续向gcd_test.go文件添加ExampleGcd函数如下:
func ExampleGcd(){
ans := Gcd(28, 70)
fmt.Println(ans)
// Output: 14
}
运行这个包的测试套件(-v选项会显示每个用例的测试结果),得到如下的结果:
可以看见,Gcd函数也通过了ExampleGcd示例函数的测试。
之后看到,上面编写的示例函数也出现在了文档中:
可以看到文档中包括之前定义的Gcd函数的函数原型,以及示例函数ExampleGcd的内容及运行结果。此时正确完成了ExampleGcd函数并将其加入了示例文档。
通过TDD实践完成快速排序算法
先写测试
快速排序算法是一种空间复杂度为 O ( n ) O(n) O(n),时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)的排序算法。其利用了分治和递归的思想,将小于等于某个数的数都划分到数组的一边,将大于某个数的数都划分到数组的另一边。之后对两边分别递归调用快速排序。这样就完成了对一个数组的排序过程。上面划分操作的时间复杂度为 O ( n ) O(n) O(n),则快速排序的时间复杂度 T ( n ) T(n) T(n)满足 T ( n ) = T ( n 2 ) + O ( n ) T(n)=T(\frac{n}{2})+O(n) T(n)=T(2n)+O(n),根据主定理, T ( n ) = O ( n l o g n ) T(n)=O(nlogn) T(n)=O(nlogn)。
这一节的工作目录在"$GOPATH/src/github.com/{your-user-id}/sort"下
由于我的github的user id为alphabstc,这一节下面显示的工作目录就在"$GOPATH/src/github.com/alphabstc/sort"之下。
先写测试
先编写对快速排序求解结果的测试。在上述工作目录下创建名为quicksort_test.go的代码文件,在文件中输入下面的代码:
package sort
import "testing"
func TestQuicksort(t *testing.T) {
res := Quicksort([]int{ 3, 5, 11, 17, 21, 23, 18, 17, 2, 14, 8, 1})
expected := []int{ 1, 2, 3, 5, 8, 11, 14, 17, 17, 18, 21, 23}
if len(res) != len(expected){
t.Errorf("plength expected: %d, got: %d", len(expected), len(res))
}
for i := 0; i < len(expected); i++ {
if res[i] != expected[i] {
t.Errorf("position %d expected: %d, got: %d", i, expected[i], res[i])
}
}
}
上面的代码检查了对一个乱序数组快速排序后的结果是否为单调不降的顺序。
尝试运行测试
在工作目录下运行“go test”进行测试,运行结果如下:
可以看到,由于目前包内还没有定义Quicksort函数,测试的结果为没有找到Quicksort函数的定义,运行失败。
先使用最少的代码来让失败的测试先跑起来
现在只需让代码可编译,这样就可以检查测试用例能否通过。
在包内,也就是之前所在的工作目录下,创建名为quicksort.go的代码文件,并且在其中输入以下内容,完成package的声明和Quicksort函数的定义。
package sort
func Quicksort(a []int) []int {
return a
}
接着再次运行测试:
以看到,现在定义了Quicksort函数,但没有具体实现其排序功能,直接将传入的数组参数返回。因此现在可以通过编译并运行测试,由于还没有实现好Quicksort函数的功能,因此测试结果是失败的。
把代码补充完整,使得它能够通过测试
接下来补充完整代码使得可以通过上面的测试。这里先修改成为冒泡排序使得可以通过测试。修改Quicksort函数代码如下:
func Quicksort(arg []int) []int {
a := make([]int, len(arg))
for i, v := range arg {
a[i] = v
}
l := len(a)
for i := 1; i < l; i++ {
for j := 0; j + 1 < l; j++ {
if a[j] > a[j + 1] {
t := a[j]
a[j] = a[j + 1]
a[j + 1] = t
}
}
}
return a
}
修改后再次进行测试:
现在可以看到通过了测试,符合要求。说明测试代码是符合要求的,实现的冒泡排序算法也正确。
基准测试
现在进一步在quicksort_test.go中定义基准测试函数的代码如下:
func BenchmarkQuicksort(b *testing.B) {
for i := 0; i < b.N; i++ {
Quicksort([]int{ 3, 5, 11, 17, 21, 23, 18, 17, 2, 14, 8, 1})
}
}
基准测试运行时,Quicksort会被调用 b.N 次,并测量需要多长时间。测试框架会选择一个它所认为的最佳值,以便获得更合理的结果。
以上结果说明在我的电脑上运行一次这个函数平均需要127纳秒。基准测试调用了它运行了9174644次。
重构
上面的冒泡排序代码通过了测试。现在重构一下代码,使用快速排序完成Quicksort函数,并检查是否仍然能通过测试。
修改quicksort.go的内容如下:
package sort
import (
"math/rand"
"time"
)
func Quicksort(arg []int) []int {
a := make([]int, len(arg))
for i, v := range arg {
a[i] = v
}
rand.Seed(time.Now().UnixNano())
myqsort(a, 0, len(a) - 1);
return a
}
func myqsort(a []int, left int, right int) {
if left >= right {
return;
}
ra := rand.Intn(right - left + 1) + left
te := a[ra]
a[ra] = a[left]
a[left] = te
i := left + 1
j := right
for {
for ; i <= right && a[i] < a[left]; {
i++
}
for ; left <= j && a[j] > a[left]; {
j--
}
if i >= j {
break;
}
te = a[j]
a[j] = a[i]
a[i] = te
}
te = a[j]
a[j] = a[left]
a[left] = te
myqsort(a, left, j - 1)
myqsort(a, j + 1, right)
}
之后再次测试:
可以看到,重构后的代码仍然可以通过测试,符合要求,说明了重构后代码的正确性。
再次运行基准测试
以上结果说明在我的电脑上运行一次这个函数平均需要10800纳秒。基准测试调用了它运行了112654次。其平均运行时间比之前的冒泡排序要长,这大概是因为快速排序使用了递归,因此在运行时需要压栈,出栈等操作,这会导致算法时间常数的增大。另一方面,为了防止快速排序退化(对于特定的数据,快速排序可能会退化为 O ( n 2 ) O(n^2) O(n2)时间复杂度的),上面的实现过程中使用了伪随机数等库函数,计算伪随机数以及随机数种子初始化也是需要较长时间的,不过通过随机化的处理,快速排序时间复杂度退化的概率将会大大降低。综合上面两方便因素,对于当前长度只有12的数组的特定测试,快速排序显得比之前的冒泡排序稍慢。但当数组的长度n趋向于很大时,快速排序的平均渐进时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),是会明显好过渐进时间复杂度为 O ( n 2 ) O(n^2) O(n2)的冒泡排序的。
修改测试代码,以便调用者可以指定按顺序或逆序排序,然后修复代码
首先修改测试代码中的TestQuicksort函数和BenchmarkQuicksort函数,修改后的测试代码quicksort_test.go调用Repeat会传入两个参数,一个为要被排序的数组,一个是布尔类型(为false时表示升序,为true时表示降序)。修改后的代码如下:
package sort
import "testing"
func TestQuicksort(t *testing.T) {
res := Quicksort([]int{ 3, 5, 11, 17, 21, 23, 18, 17, 2, 14, 8, 1}, false)
expected := []int{ 1, 2, 3, 5, 8, 11, 14, 17, 17, 18, 21, 23}
if len(res) != len(expected){
t.Errorf("plength expected: %d, got: %d", len(expected), len(res))
}
for i := 0; i < len(expected); i++ {
if res[i] != expected[i] {
t.Errorf("position %d expected: %d, got: %d", i, expected[i], res[i])
}
}
res = Quicksort([]int{ 3, 5, 11, 17, 21, 23, 18, 17, 2, 14, 8, 1}, true)
expected = []int{ 23, 21, 18, 17, 17, 14, 11, 8, 5, 3, 2, 1}
if len(res) != len(expected){
t.Errorf("plength expected: %d, got: %d", len(expected), len(res))
}
for i := 0; i < len(expected); i++ {
if res[i] != expected[i] {
t.Errorf("position %d expected: %d, got: %d", i, expected[i], res[i])
}
}
}
func BenchmarkQuicksort(b *testing.B) {
for i := 0; i < b.N; i++ {
Quicksort([]int{ 3, 5, 11, 17, 21, 23, 18, 17, 2, 14, 8, 1}, false)
}
}
然后再次运行测试,会提示如下的错误:
上面的错误信息提示说,传入Quicksort函数的参数过多。根据提示,现在修复Quicksort函数的代码使其可以根据传入的参数支持升序和降序排序,修改后的代码如下:
func Quicksort(arg []int, rev bool) []int {
a := make([]int, len(arg))
for i, v := range arg {
a[i] = v
}
rand.Seed(time.Now().UnixNano())
myqsort(a, 0, len(a) - 1);
if rev {
le := len(a)
for i:= 0; i + i < le; i++ {
te := a[i]
a[i] = a[le - i - 1]
a[le - i - 1] = te
}
}
return a
}
再次运行测试和基准测试:
可以看到,现在可以正常运行测试和基准测试。上面的结果说明通过了测试;且基准测试测量出运行一次Quicksort函数平均需要10605ns。为了测量出平均用时,调用了Quicksort函数105500次。
这样就实现了,可以指定升序排序还是降序排序的功能。
写一个示例函数来完善函数文档
继续向quicksort_test.go文件添加ExampleQuicksort函数如下:
func ExampleQuicksort(){
ans := Quicksort([]int{ 1, 3, 5, 3, 2, 4}, false)
fmt.Println(ans)
ans = Quicksort([]int{ 1, 3, 5, 3, 2, 4}, true)
fmt.Println(ans)
// Output: [1 2 3 3 4 5]
//[5 4 3 3 2 1]
}
运行这个包的测试套件(-v选项会显示每个用例的测试结果),得到如下的结果:
可以看见,Quicksort函数也通过了ExampleQuicksort示例函数的测试。
之后看到,上面编写的示例函数也出现在了文档中:
可以看到文档中包括之前定义的Quicksort函数的函数原型,以及示例函数ExampleQuicksort的内容及运行结果。此时正确完成了ExampleQuicksort函数并将其加入了示例文档。
小结
TDD是测试驱动开发(Test-Driven Development)的缩写。通过这次实验,我学习并熟悉了TDD,重构,测试,基准测试的概念。通过代码实践我进一步熟悉了go的if语句,for语句,函数定义,数组声明与传递参数等语法。我也在实践中进一步熟悉了go中工作空间,包,测试,基准测试,示例函数的概念,并了解了如何编写这些内容。通过阅读Go语言的教学指导以及通过TDD实践完成教程“迭代”章节的练习,欧几里得算法,快速排序算法,我也进一步熟悉了TDD的开发流程,更深刻体会到了测试驱动开发理念的重要作用与意义。