## 27.测试驱动开发
假设你在马戏团中,一个漂亮的女孩正在空中飞人表演,她错过了抓地力并摔倒了,你希望那里有一个安全网来捉住她吗? 你必须疯了才能拒绝。 以类似的方式,可以说你正在开发软件,犯了一个错误,使用该软件的人会付出很多代价,拥有制衡能力不是一件好事,这样即使在错误发生之前就可以知道该错误。 软件已发货?
欢迎来到测试驱动开发。 在这种方法中,我们首先编写测试,然后编写足够的代码来满足测试。 通过遵循这种方法,我能够以最大的信心进行编码,并且能够知道存在安全网以防万一我做错了事,从而能够更改代码并使之更好(也称为重构)。
让我们想象一个场景。 你的任务是编写聊天机器人程序,其初始要求如图所示
* 必须有一个聊天机器人
* 一个人必须能够设定年龄和名字
* 它的问候语必须是:“你好,我叫<,名字叫>,我的年龄是<,年龄>,很高兴认识你!”
通常,要求不会像上面显示的那样精确,但是作为一名程序员,应该可以考虑一下。 现在,我们开始编写测试文件,而不是编写代码来解决任务,将其命名为 test_chat_bot.rb 并将需求放入其中,如下所示:
```rb
# test_chat_bot.rb
# There must be a chat bot
# One must be able to set its age
# One must be able to set its name
# Its greeting must say "Hello I am <name> and my age is <age>.
# Nice to meet you!"
```
现在,这些要求已在我们的程序中变成了单词,我们需要将其转换为 Ruby。 几乎所有编程语言都内置了一个测试框架,Ruby 也有一个测试框架,其名称为 Minitest <sup class="footnote">[ [63](#_footnotedef_63 "View footnote.") ]</sup> 。 我们将在这里使用它。 为了包括 Minitest,我们添加下面突出显示的行
```rb
# test_chat_bot.rb
require "minitest/autorun"
# There must be a chat bot
# One must be able to set its age
# One must be able to set its name
# Its greeting must say "Hello I am <name> and my age is <age>.
# Nice to meet you!"
```
现在我们包含了 Minitest,现在让我们编写一个测试类,如下所示
```rb
# test_chat_bot.rb
require "minitest/autorun"
class TestChatBot < Minitest::Test
# There must be a chat bot
# One must be able to set its age
# One must be able to set its name
# Its greeting must say "Hello I am <name> and my age is <age>.
# Nice to meet you!"
end
```
完成上面显示的内容后,现在让我们为第一个测试用例编写代码,看看下面的代码
```rb
class TestChatBot < Minitest::Test
# There must be a chat bot
def test_there_must_be_a_chat_bot
assert_kind_of ChatBot, ChatBot.new
end
# One must be able to set its age
# One must be able to set its name
# Its greeting must say "Hello I am <name> and my age is <age>.
# Nice to meet you!"
end
```
上面的代码中发生了很多事情,首先我们通过编写一个测试函数
```rb
def test_there_must_be_a_chat_bot
end
```
注意此功能如何以`test_`开头,这对于将其识别为测试至关重要。 接下来,我们必须测试此功能中的某些内容。 只有在`ChatBot`类存在的情况下,我们才能使用`ChatBot`类的实例,因此我们尝试在下面的突出显示的代码中创建一个新的`ChatBot`实例,并检查其类是否为`ChatBot`。
```rb
def test_there_must_be_a_chat_bot
assert_kind_of ChatBot, ChatBot.new
end
```
如果你想知道这些内容,让我来解释一下`assert_kind_of`。 这些称为断言。 你可以在此处查看哪些断言 [http://ruby-doc.org/stdlib-2.0.0/libdoc/minitest/rdoc/MiniTest/Assertions.html#method-i-assert_respond_to](http://ruby-doc.org/stdlib-2.0.0/libdoc/minitest/rdoc/MiniTest/Assertions.html#method-i-assert_respond_to) 。
断言是用于验证天气是否发生某些预期事件的函数,如果发生,则表示测试已通过,否则测试已失败。
现在让我们运行测试文件 test_chat_bot.rb
```rb
$ ruby test_chat_bot.rb
Run options: --seed 53866
# Running:
E
Finished in 0.000875s, 1142.2906 runs/s, 0.0000 assertions/s.
1) Error:
TestChatBot#test_there_must_be_a_chat_bot:
NameError: uninitialized constant TestChatBot::ChatBot
test_chat_bot.rb:9:in `test_there_must_be_a_chat_bot'
1 runs, 0 assertions, 0 failures, 1 errors, 0 skips
```
因此,我们得到上面的输出,表明不存在名为`ChatBot`的常量。 很好,我们尚未定义`ChatBot`是什么,因此我们将对其进行定义。 在 test_chatbot.rb 中,我们添加`require_relative "chat_bot.rb"`行,如下所示
```rb
# test_chat_bot.rb
require "minitest/autorun"
require_relative "chat_bot.rb"
class TestChatBot < Minitest::Test
# There must be a chat bot
def test_there_must_be_a_chat_bot
assert_kind_of ChatBot, ChatBot.new
end
# One must be able to set its age
# One must be able to set its name
# Its greeting must say "Hello I am <name> and my age is <age>.
# Nice to meet you!"
end
```
然后,我们创建一个名为 chat_bot.rb 的新文件,其中包含以下内容
```rb
# chat_bot.rb
class ChatBot
end
```
现在运行测试。
```rb
$ ruby test_chat_bot.rb
Run options: --seed 19585
# Running:
.
Finished in 0.000720s, 1388.5244 runs/s, 1388.5244 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
```
我们做了什么? 我们有一个需求,我们编写了涵盖该需求的测试,我们运行了测试,但失败了,通过了,我们编写了足以使它通过的代码。 现在想象一个场景,你正在一个有 10 个开发人员的项目中,其中一个偶然地犯了一个错误,该错误将重命名此`ChatBot`类,并且你的测试将抓住它。 简而言之,如果你编写了足够的测试,则可以提早发现错误。 它不能保证没有错误的代码,但是会使错误弹出更加困难。 这些测试还将使你有信心重构代码。 假设你进行了更改,则不必担心你的更改可能会造成严重破坏,只需运行测试一次,你将获得有关失败和通过的报告。
让我们编写另一个测试,一个应该能够给聊天机器人一个`age`。 因此,让我们编写一个测试,在其中可以设置它的年龄并重新读取它。 查看下面的函数`test_one_must_be_able_to_set_its_age`中的代码
```rb
# test_chat_bot.rb
require "minitest/autorun"
require_relative "chat_bot.rb"
class TestChatBot < Minitest::Test
# There must be a chat bot
def test_there_must_be_a_chat_bot
assert_kind_of ChatBot, ChatBot.new
end
# One must be able to set its age
def test_one_must_be_able_to_set_its_age
age = 21
chat_bot = ChatBot.new
chat_bot.age = age
assert_equal age, chat_bot.age
end
# One must be able to set its name
# Its greeting must say "Hello I am <name> and my age is <age>.
# Nice to meet you!"
end
```
看上面的代码,看这行`assert_equal age, chat_bot.age`,在这里我们断言 chat_bot 返回设置的年龄。
```rb
$ ruby test_chat_bot.rb
Run options: --seed 59168
# Running:
.E
Finished in 0.000855s, 2338.4784 runs/s, 1169.2392 assertions/s.
1) Error:
TestChatBot#test_one_must_be_able_to_set_its_age:
NoMethodError: undefined method `age=' for #<ChatBot:0x0000558b89da5380>
test_chat_bot.rb:16:in `test_one_must_be_able_to_set_its_age'
2 runs, 1 assertions, 0 failures, 1 errors, 0 skips
```
如果你看到上面的测试结果,则说明没有方法错误,并说明缺少函数`age=`,因此请对其进行修复。
```rb
# chat_bot.rb
class ChatBot
attr_accessor :age
end
```
因此,我们在`attr_accessor :age`行上方添加了内容,然后运行测试并通过了如下所示的测试
```rb
$ ruby test_chat_bot.rb
Run options: --seed 42767
# Running:
..
Finished in 0.000774s, 2583.4820 runs/s, 2583.4820 assertions/s.
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
```
现在让我们在名称上也做同样的事情,在年龄上我将不做解释,在本书中也不再赘述。 让我说说最后的测试。 它必须打招呼。 为此,我们编写了一个测试,如下面的函数`test_greeting_message`所示:
```rb
# test_chat_bot.rb
require "minitest/autorun"
require_relative "chat_bot.rb"
class TestChatBot < Minitest::Test
# There must be a chat bot
def test_there_must_be_a_chat_bot
assert_kind_of ChatBot, ChatBot.new
end
# One must be able to set its age
def test_one_must_be_able_to_set_its_age
age = 21
chat_bot = ChatBot.new
chat_bot.age = age
assert_equal age, chat_bot.age
end
# One must be able to set its name
def test_one_must_be_able_to_set_its_name
name = "Zigor"
chat_bot = ChatBot.new
chat_bot.name = name
assert_equal name, chat_bot.name
end
# Its greeting must say "Hello I am <name> and my age is <age>.
# Nice to meet you!"
def test_greeting_message
name = "Zigor"
age = 21
expected_message = "Hello I am #{name} and my age is #{age}. Nice to meet you!"
chat_bot = ChatBot.new
chat_bot.name = name
chat_bot.age = age
assert_equal expected_message, chat_bot.greeting_message
end
end
```
现在我们运行它,如你所见,它自然会失败,如下所示
```rb
$ ruby test_chat_bot.rb
Run options: --seed 8752
# Running:
.E..
Finished in 0.001075s, 3720.5045 runs/s, 2790.3784 assertions/s.
1) Error:
TestChatBot#test_greeting_message:
NoMethodError: undefined method `greeting_message' for #<ChatBot:0x000055b4ae5a8620 @name="Zigor", @age=21>
test_chat_bot.rb:39:in `test_greeting_message'
4 runs, 3 assertions, 0 failures, 1 errors, 0 skips
```
因此我们修改了文件 [chat_bot.rb](code/chat_bot.rb) ,如下所示
```rb
# chat_bot.rb
class ChatBot
attr_accessor :age, :name
def greeting_message
"Hello I am #{name} and my age is #{age}. Nice to meet you!"
end
end
```
现在我们运行测试,它通过如下所示:
```rb
$ ruby test_chat_bot.rb
Run options: --seed 16324
# Running:
....
Finished in 0.001149s, 3480.2007 runs/s, 3480.2007 assertions/s.
4 runs, 4 assertions, 0 failures, 0 errors, 0 skips
```
因此,现在我们有了一个应用程序和一个安全网,因此它具有更多的错误证明。
# 设计模式
**需要设计模式**
与现在使用的软件相比,当软件开始时,它很小,计算机功率很低,并且只能用于非常艰巨的任务,所以人们对单个人或紧密联系的小组可以维护的小程序感到满意。 但是,随着计算机变得越来越强大,计算机变得越来越复杂,项目变得越来越庞大,代码的结构成为一个重要的问题。 那就是设计模式揭晓的时候。
阅读本书的大多数人可能是 Ruby 初学者或中级,但是你可能需要从事实际项目。 即使你为个人项目选择了 Ruby,也更好地组织代码,因此对设计模式的需求变得至关重要。
- 前言
- 红宝石
- 先决条件
- 1.安装 Ruby
- 2.在线资源
- 3.入门
- 4.比较与逻辑
- 5.循环
- 6.数组
- 7.哈希和符号
- 8.范围
- 9.功能
- 10.可变范围
- 11.类&对象
- 12.安全导航
- 13.打破大型程序
- 14.结构和 OpenStruct
- 15. Rdoc
- 16. Ruby 样式指南
- 17.模块和混入
- 18.日期和时间
- 19.文件
- 20. Proc,Lambda 和块
- 21.多线程
- 22.异常处理
- 23.正则表达式
- 24.宝石
- 25.元编程
- 26.基准
- 27.测试驱动开发
- 28.观察者模式
- 29.模板模式
- 30.工厂模式
- 31.装饰图案
- 32.适配器模式
- 33.单例模式
- 34.复合模式
- 35.建造者模式
- 36.策略模式
- 赞助商
- 捐
- 人们怎么说
- 版权
- 取得这本书