## 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.策略模式
- 贊助商
- 捐
- 人們怎么說
- 版權
- 取得這本書