![测试驱动开发:入门、实战与进阶](https://wfqqreader-1252317822.image.myqcloud.com/cover/702/48593702/b_48593702.jpg)
1.4 让测试通过
我们刚才写测试的时候,暂时忽略了语法上有可能出现编译错误的所有地方,只想着把自己期望的结果表达出来。这样做合适吗?
在刚刚开始的时候,通过极少量的代码来摆正我们的前进方向确实是合适的,我们目前所处的正是这种刚刚开始的状态。当然,由于还没定义Dollar就开始使用它,因此测试会失败。这时有人可能会来一句:“那还用说?”然而大家目前还是得稍微有一点点耐心才行,因为我们至少做到了这样两点:
1.我们已经完成了第一步,也就是让第一个测试变红(或者说,写出了第一个失败的测试)。对于所要实现的每一个功能来说,编写失败的测试都是实现该功能时的第一步,而我们现在所要实现的是整个程序的第一个功能,因此我们不仅处在这个功能的起点,而且处在整个程序的起点。
2.我们可以(而且乐意)在开发后续功能的时候,逐渐提升实现每个功能的速度。然而我们同时也知道在需要放慢脚步的时候可以慢下来。
RGR环的第二个环节是让测试变绿(也就是令其通过)。
我们显然需要抽象出这个名为Dollar的概念。这一节就定义如何引入此抽象和其他一些必要的抽象,让我们的测试能够通过。
1.4.1 Go
在money_test.go末尾添加一个空白的Dollar结构体:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/48_03.jpg?sign=1738885720-b39QByim4KtaaTZj2V6JSQ8zcUrhZNwD-0-b1a10e20c10f8884bf7fbb0021399fcf)
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/48_04.jpg?sign=1738885720-UayUjlanga3PDKFO2acr1k6Rc1OIhj4q-0-fb6b5b07310ad742e51ffb63b83c51f4)
这次运行测试,我们会看到一条新的错误消息:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/48_05.jpg?sign=1738885720-dFyOEPXfYwNPkc6eVG4B3JOhyi2V0Vw2-0-c6a6a06b3a3099ba8000e7ff5884f67c)
不错,有进展了!
这条错误消息指引我们给Dollar结构体添加名为amount的字段。我们现在就做。对于当前的目标来说,只需要用int类型设计这个字段就足够了:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/49_01.jpg?sign=1738885720-AwJYfO5LTXNdjLKZloUIYptDl5nCUQ7Y-0-b9ae7c1fe45183be9d6872e9f832b2bb)
把这个字段添加到Dollar结构体之后,接下来运行测试的时候当然就会遇到这样一条错误消息:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/49_02.jpg?sign=1738885720-VxVvcEZqV3NfbvhzytK0qDYITnuIEb2X-0-c18df2b84876234be3b14774dfafebf3)
大家可以看到一条规律:如果还没有定义某个东西(例如某个字段或方法)时就使用它,那么Go语言的运行时库会给出undefined错误。以后我们会利用这条规律来提升TDD的速度,从而更快地走完每一个RGR环。现在,我们先添加名叫Times的函数。根据我们所写的测试,这个函数必须接受一个(表示乘数的)数字,并返回另一个(表示相乘结果的)数字。
然而,这个结果应该怎样计算呢?我们当然知道基本的算术规则,也就是说,我们知道怎样用编程手段来计算两数相乘之积。但是,现在只需要用最简单的代码让测试通过就行了,因此我们完全可以直接返回测试所期望的那个值,即一个用来表示10美元的结构体:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/49_03.jpg?sign=1738885720-p7XTQafwBcOhDnlc0PiZCL7J0h2rmrNo-0-3fc8716fc7f978006d6129d010c90360)
再度运行测试,我们会在终端中看到一条简短而令人开心的回应:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/49_04.jpg?sign=1738885720-W2YB3vROkNhLoPUvCezb5JUezJtMDfFx-0-d0eac32798ba352396258f079325c7c6)
其中最关键的词就是PASS,这表示我们的测试通过了!
1.4.2 JavaScript
打开test_money.js文件,找到const assert=require('assert');这行代码,在它下面紧接着定义一个空白的Dollar类:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/49_05.jpg?sign=1738885720-dvMvJ5TZOWNCmWuiYMwARYCcorRZeaN5-0-245811b616f867ed90e7a3739f89c129)
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/49_06.jpg?sign=1738885720-Tiu0FcKyiRgeoAfThyneZfpicJW4Inyl-0-4cb21ba200e8d2e7661a0e56e849a848)
运行test_money.js时,我们会看到这样一条错误消息:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/50_01.jpg?sign=1738885720-AowiHRmZPSjmi0CF14jaZBpZMawAdiIg-0-544a670516a1af5de60fde3ef0bff50e)
有进步!这条错误消息清楚地告诉我们,目前还没有给这个叫作fiver的对象定义名为times的方法。于是,我们现在就向Dollar类添加这样的方法:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/50_02.jpg?sign=1738885720-vnpmQ5jpo9noC9vNYbA76zGe0usUZcWM-0-543c79dab425028089d0234373803862)
这次运行测试,我们会看到一条新的错误消息:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/50_03.jpg?sign=1738885720-tPvLMcbStcoS0EsAHaj9f7m9zWRexfZk-0-67950ff6ab5feb9c5de37c3f50eb18ab)
❶这是Node.js v16所给出的错误消息,如果用的是v14,那么错误消息会稍有不同。
我们的测试希望times方法能够返回一个带有amount属性的对象。然而刚才写times方法时却没有让该方法返回任何值,因此JavaScript会把返回值判定为undefined,这样一个值当然没有名叫amount的属性(其实不单是这个属性,它同样没有其他名字的属性)。
JavaScript语言的函数与方法不会明确声明返回值的类型。如果某个函数根本就不返回任何东西,而我们又查看了该函数的返回值,那么这样查出来的返回值就是undefined。
怎样才能让测试变绿?要想做到这一点,最简单的方法是什么?是不是可以考虑让times函数总创建一个表示10美元的对象并返回该对象?
现在就试试看。我们添加一个constructor(构造器),用来将本对象的amount属性初始化成指定的值,然后让times方法总是通过调用这个构造器来创建一个表示10美元的Dollar对象:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/50_05.jpg?sign=1738885720-wrPuONrTx1MvnVByJo59Der6jPBuzJsl-0-56ac47227671637591421eef04fdd630)
❶每当创建Dollar对象的时候,constructor都会得到调用。
❷以调用方所给的参数来初始化this.amount变量。
❸times方法需要接受一个参数。
❹采用最简单的办法实现该方法,也就是让它总是返回一个表示10美元的对象。
现在运行这段代码,看不到任何错误。这说明我们的测试变绿了!
assert包中的strictEqual方法与其他方法都只在断言失败的情况下才给出错误消息,如果测试成功,那么这些方法不会有任何输出。我们将在第6章改进这一点。
1.4.3 Python
由于Dollar还没有得到定义,因此我们需要在test_money.py文件里面定义这样一个类。我们把这个类写在TestMoney类的前面:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/51_02.jpg?sign=1738885720-OVNNa7UTOUy36RrMXznoNmVhyKLhtdfY-0-e509c10db1be0fb7dec0244d8e7b3f0b)
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/51_03.jpg?sign=1738885720-6cRt2IJ8NZwJV9IIg0Ca31qw6KBucCzV-0-8841701ac5e521bd4542bdf14b7294a0)
运行代码,我们会看到这样一条错误消息:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/51_04.jpg?sign=1738885720-OxdToeoMp7Snj8YCFX8MA7dSN0rMLqH7-0-023518832c9edefdffa4e7506cdda4b2)
有进展!这条错误消息清楚地告诉我们:目前还没有办法用参数来初始化Dollar对象(我们在代码里面需要用5或10这样的参数来初始化相应的Dollar对象)。现在,编写一种最简单的初始化器(initializer)来解决这个问题:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/51_05.jpg?sign=1738885720-R7ZB67u4G7tjf6Fs9U3f4gTU0v5r57Ol-0-1421e476e50b96af3f5e6c77483bf19e)
运行测试,我们发现它所产生的错误消息已经变了:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/51_06.jpg?sign=1738885720-ITYoZHEt96VKrO5m4MNFxjfAzTiTi49p-0-5954ae956b0f166aaa5156ede6ded408)
我们在这里发现一条规律:尽管测试依然失败,但每次失败的原因都略有不同。一开始是因为没有定义Dollar类,于是我们定义这样一个类,后来又变成没有能够接收参数的构造器,于是我们定义这样一个能够接收参数的构造器。现在,又变成了没有times方法,每次的错误消息都促使我们改进现有的代码,把它推进到一个更好的状态之中。这正是TDD的特征,也就是以我们自己所控制的节奏稳步向前推进。
现在我们稍微提升一下速度,把两件事放到一起做:一是定义名为times的函数,二是用最简单的方式实现该函数,以便让整个测试变绿。那么,什么是最简单的方式呢?当然是让这个函数总返回测试所要求的结果,也就是返回一个表示10美元的对象。
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/52_01.jpg?sign=1738885720-zeQysnSpa0dQOV1eO8Kn1OlwyuujBPg3-0-448d868b22765fe35c00ae9946a4a69c)
❶只要一创建Dollar对象,__init__函数就会得到调用。
❷以调用方给出的参数来初始化self.amount变量。
❸让times方法接受一个参数。
❹我们用最简单的方式实现times方法,也就是总让该方法返回一个表示10美元的Dollar对象。
运行测试,我们会看到一段简短而可喜的回应:
![](https://epubservercos.yuewen.com/2F0B0F/28235549002740206/epubprivate/OEBPS/Images/52_02.jpg?sign=1738885720-rAl320OjF96hAEC87qSS6s8nILY0YKeA-0-c9a213988353858a596ff473f3773dae)
这里面写的时间可能稍微有点夸张,运行测试所需的时长应该要比0.000s多。但是别忘了,我们的重点是OK。这表示我们的第一个测试已经变绿了!