36.md 10.9 KB
Newer Older
W
init  
wizardforcel 已提交
1 2
## 27.测试驱动开发

W
wizardforcel 已提交
3
假设你在马戏团中,一个漂亮的女孩正在空中飞人表演,她错过了抓地力并摔倒了,你希望那里有一个安全网来捉住她吗? 你必须疯了才能拒绝。 以类似的方式,可以说你正在开发软件,犯了一个错误,使用该软件的人会付出很多代价,拥有制衡能力不是一件好事,这样即使在错误发生之前就可以知道该错误。 软件已发货?
W
init  
wizardforcel 已提交
4 5 6

欢迎来到测试驱动开发。 在这种方法中,我们首先编写测试,然后编写足够的代码来满足测试。 通过遵循这种方法,我能够以最大的信心进行编码,并且能够知道存在安全网以防万一我做错了事,从而能够更改代码并使之更好(也称为重构)。

W
wizardforcel 已提交
7
让我们想象一个场景。 你的任务是编写聊天机器人程序,其初始要求如图所示
W
init  
wizardforcel 已提交
8 9 10 11 12

*   必须有一个聊天机器人

*   一个人必须能够设定年龄和名字

W
wizardforcel 已提交
13
*   它的问候语必须是:“你好,我叫<,名字叫>,我的年龄是<,年龄>,很高兴认识你!”
W
init  
wizardforcel 已提交
14 15 16

通常,要求不会像上面显示的那样精确,但是作为一名程序员,应该可以考虑一下。 现在,我们开始编写测试文件,而不是编写代码来解决任务,将其命名为 test_chat_bot.rb 并将需求放入其中,如下所示:

W
wizardforcel 已提交
17
```rb
W
init  
wizardforcel 已提交
18 19 20 21 22 23 24 25 26 27 28 29 30 31
# 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 &lt;sup class="footnote"&gt;[ [63](#_footnotedef_63 "View footnote.") ]&lt;/sup&gt; 。 我们将在这里使用它。 为了包括 Minitest,我们添加下面突出显示的行

W
wizardforcel 已提交
32
```rb
W
init  
wizardforcel 已提交
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
# 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,现在让我们编写一个测试类,如下所示

W
wizardforcel 已提交
49
```rb
W
init  
wizardforcel 已提交
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
# 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
```

完成上面显示的内容后,现在让我们为第一个测试用例编写代码,看看下面的代码

W
wizardforcel 已提交
68
```rb
W
init  
wizardforcel 已提交
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
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
```

上面的代码中发生了很多事情,首先我们通过编写一个测试函数

W
wizardforcel 已提交
86
```rb
W
init  
wizardforcel 已提交
87 88 89 90 91 92
 def test_there_must_be_a_chat_bot
  end
```

注意此功能如何以`test_`开头,这对于将其识别为测试至关重要。 接下来,我们必须测试此功能中的某些内容。 只有在`ChatBot`类存在的情况下,我们才能使用`ChatBot`类的实例,因此我们尝试在下面的突出显示的代码中创建一个新的`ChatBot`实例,并检查其类是否为`ChatBot`

W
wizardforcel 已提交
93
```rb
W
init  
wizardforcel 已提交
94 95 96 97 98
 def test_there_must_be_a_chat_bot
    assert_kind_of ChatBot, ChatBot.new
  end
```

W
wizardforcel 已提交
99
如果你想知道这些内容,让我来解释一下`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)
W
init  
wizardforcel 已提交
100 101 102 103 104

断言是用于验证天气是否发生某些预期事件的函数,如果发生,则表示测试已通过,否则测试已失败。

现在让我们运行测试文件 test_chat_bot.rb

W
wizardforcel 已提交
105
```rb
W
init  
wizardforcel 已提交
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
$ 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"`行,如下所示

W
wizardforcel 已提交
125
```rb
W
init  
wizardforcel 已提交
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
# 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 的新文件,其中包含以下内容

W
wizardforcel 已提交
148
```rb
W
init  
wizardforcel 已提交
149 150 151 152 153 154 155 156
# chat_bot.rb

class ChatBot
end
```

现在运行测试。

W
wizardforcel 已提交
157
```rb
W
init  
wizardforcel 已提交
158 159 160 161 162 163 164 165 166 167 168 169
$ 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
```

W
wizardforcel 已提交
170
我们做了什么? 我们有一个需求,我们编写了涵盖该需求的测试,我们运行了测试,但失败了,通过了,我们编写了足以使它通过的代码。 现在想象一个场景,你正在一个有 10 个开发人员的项目中,其中一个偶然地犯了一个错误,该错误将重命名此`ChatBot`类,并且你的测试将抓住它。 简而言之,如果你编写了足够的测试,则可以提早发现错误。 它不能保证没有错误的代码,但是会使错误弹出更加困难。 这些测试还将使你有信心重构代码。 假设你进行了更改,则不必担心你的更改可能会造成严重破坏,只需运行测试一次,你将获得有关失败和通过的报告。
W
init  
wizardforcel 已提交
171 172 173

让我们编写另一个测试,一个应该能够给聊天机器人一个`age`。 因此,让我们编写一个测试,在其中可以设置它的年龄并重新读取它。 查看下面的函数`test_one_must_be_able_to_set_its_age`中的代码

W
wizardforcel 已提交
174
```rb
W
init  
wizardforcel 已提交
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
# 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 返回设置的年龄。

W
wizardforcel 已提交
203
```rb
W
init  
wizardforcel 已提交
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
$ 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
```

W
wizardforcel 已提交
221
如果你看到上面的测试结果,则说明没有方法错误,并说明缺少函数`age=`,因此请对其进行修复。
W
init  
wizardforcel 已提交
222

W
wizardforcel 已提交
223
```rb
W
init  
wizardforcel 已提交
224 225 226 227 228 229 230 231 232
# chat_bot.rb

class ChatBot
  attr_accessor :age
end
```

因此,我们在`attr_accessor :age`行上方添加了内容,然后运行测试并通过了如下所示的测试

W
wizardforcel 已提交
233
```rb
W
init  
wizardforcel 已提交
234 235 236 237 238 239 240 241 242 243 244 245 246 247
$ 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`所示:

W
wizardforcel 已提交
248
```rb
W
init  
wizardforcel 已提交
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
# 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
```

W
wizardforcel 已提交
290
现在我们运行它,如你所见,它自然会失败,如下所示
W
init  
wizardforcel 已提交
291

W
wizardforcel 已提交
292
```rb
W
init  
wizardforcel 已提交
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
$ 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) ,如下所示

W
wizardforcel 已提交
312
```rb
W
init  
wizardforcel 已提交
313 314 315 316 317 318 319 320 321 322 323 324 325
# 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
```

现在我们运行测试,它通过如下所示:

W
wizardforcel 已提交
326
```rb
W
init  
wizardforcel 已提交
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
$ 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
```

因此,现在我们有了一个应用程序和一个安全网,因此它具有更多的错误证明。

# 设计模式

**需要设计模式**

与现在使用的软件相比,当软件开始时,它很小,计算机功率很低,并且只能用于非常艰巨的任务,所以人们对单个人或紧密联系的小组可以维护的小程序感到满意。 但是,随着计算机变得越来越强大,计算机变得越来越复杂,项目变得越来越庞大,代码的结构成为一个重要的问题。 那就是设计模式揭晓的时候。

W
wizardforcel 已提交
347
阅读本书的大多数人可能是 Ruby 初学者或中级,但是你可能需要从事实际项目。 即使你为个人项目选择了 Ruby,也更好地组织代码,因此对设计模式的需求变得至关重要。