首頁 >web前端 >js教程 >Nodejs學習筆記之測試驅動程式_node.js

Nodejs學習筆記之測試驅動程式_node.js

WBOY
WBOY原創
2016-05-16 16:03:341094瀏覽

分享第二章,關於測試驅動。這裡的測試主要針對Web後端的測試- 為什麼要寫測試案例(即測試案例的完善是否是浪費時間),如何完善你的測試案例,程式碼設計如何簡化測試案例的書寫,以及一些後期的構想。

1. 為什麼要寫測試案例

這個習慣通常會被認為是一種耽誤開發進度的行為,你需要花費幾乎和開發程式碼相同的時間來逐步完善你的測試案例。但是在開發過程中,在開發完成一段程式碼後如果負責任而不是說完全把問題交給測試人員去發現的話,這個時候通常都會去做一些手動的測試。例如:

在程式碼中執行某些方法,查看輸出的值是否符合預期。
修改資料庫/緩存,然後執行某些方法,看資料庫的變化是否符合預期。
使用工具模擬請求某些接口,查看接口的回傳值/資料庫的變化值是否會符合預期。
如果有前端頁面的話,還會涉及到前後端聯調,即要在前端頁面上通過前端交互,查看前端的反饋是否符合預期,來間接驗證後端代碼的正確性。
現代化的測試工具都在盡可能的將這些人工的手動測試行為抽象化成程式碼區塊,當你有意識去進行手動測試的時候,其實已經開始在嘗試測試用例的行為了。既然可以透過手動的方式進行測試,為什麼還需要用程式碼來實現測試?

程式碼是可以重複使用或是在簡單重構後可以實現更多的功能的,但是當你選擇手動的時候,每次你都需要重頭開始。
成熟的工作流程應包含程式碼審核流程,程式碼審核的方式有很多,逐句閱讀你的程式碼,或檢查你測試程式碼的完善性以及正確性,然後執行你的測試案例。後者更加簡單。
當程式碼改動,例如修復 Bug 時候,很難保證你的改變是否會影響其他依賴你程式碼的部分。在人工測試的時代有一個叫做回歸測試,即在你修復 Bug 將你的系統重新測試一遍。但是如果你已經有了完善的測試案例了呢,直接執行指令搞定。
當你重構程式碼的時候,同上。

2. 如何完善你的測試案例

在進入完善階段前,先說說你將如何實作測試案例。

describe Meme do

 before do
  @meme = Meme.new
 end

 describe "when asked about cheeseburgers" do
  it "must respond positively" do
   @meme.i_can_has_cheezburger?.must_equal "OHAI!"
  end
 end

 describe "when asked about blending possibilities" do
  it "won't say no" do
   @meme.will_it_blend?.wont_match /^no/i
  end
 end
end

上面的程式碼來自於 Ruby 的 minitest。 before 包含的程式碼區塊是在執行下面的測試案例之前要做的事情,通常也會支援一個相對應的方法,在測試案例執行完執行。每個用例裡面都進行一些很小的判斷。

第一段提到了一些手動測試裡面常常會涉及到的測試內容,這裡拿其中的 2 和 3 進行說明。在進行資料庫相關的測試時,需要在 before 中插入一條測試數據,並且在 after 中刪除測試數據。在中間的測試案例中,透過執行對應的方法,執行完畢後:檢查資料變化狀況/檢查是否有預期的異常/是否回傳預期結果 來確認程式碼的正確性。如果是介面的話,就是透過程式碼發起對應的請求,然後檢查回傳的內容是否回傳預期,有需要的話再去查看資料庫裡面的資料是否符合預期變化。

現在已經有了測試案例,但是任然需要考慮一種特殊情況。我現在為一個函數寫了相對完善的測試案例了,跑完都 PASS 了,結果發現線上的日誌裡面還是有那個函數的報錯。檢查下發現函數的某個分支之前在測試的時候沒有測試到,剛好線上的某種情況運行到了這個分支,結果有一個很不明顯的語法錯誤報錯了,有沒有辦法能確保所有的程式碼都測試過了?這裡需要引入的是一個叫做 測試案例覆蓋率 的概念,基本上每個語言都會有回應的實作。透過測試使用案例覆蓋率,量化的告訴你你的測試案例有沒有跑完某某文件裡的所有程式碼,而你需要做的,就是盡可能保證你的覆蓋率保持在 100%。

某種意義上來說,測試案例和測試覆蓋率是用來提高開發者對自己程式碼自信心的工具。但是,他們也不是萬能的。測試案例裡面總可能會遺漏一些參數的可能性,當然你的程式碼裡面也沒有為這種可能性進行程式碼的編寫,最終測試用例覆蓋率只能告訴你你寫的程式碼我們都幫你偵測過了測試過了,對於你沒有考慮到的可能性,表示無能為力。所以盡可能寫出嚴格的程式碼,例如 javascript 裡面盡可能都用 === 而不是 ==,使用強型別的程式規格等等,這些來降低這種因為接受的參數範圍過大帶來的潛在風險。

3. 程式碼設計如何簡化測試案例的書寫

整個 Web (也不限於 web)通常包括三個層面的程式碼 —— 單純資料處理與運算、涉及資料庫、涉及到特定的網路協定。其中單純的資料運算於處理主要為普通的運算的函數或是其他程式碼,涉及到資料庫就是傳統意義上 MVC 裡面的 M,涉及到具體的網路協定就是對應的 C。這三塊的測試分別對應著第一節中常規的測試內容的前三條。

因為C層面通常也可能涉及到頁面的渲染以及對應協議的模擬,所以通常把測試的重心放在函數以及資料庫相關的程式碼裡面可以減少測試案例程式碼的複雜度,這個就要求Controller 的程式碼要盡可能少。對於複雜度較高的應用的一些目前的一些建議:

將資料的基礎校驗都放在 M層,如果使用 Ruby 開發的話,ActiveRecord以及Mongoid都提供了很方便使用的 validation 功能。
嘗試在程式碼中使用 Pub/Sub 模式來配合一些 ORM中提供的鉤子(hook) 來實現 Model 之間的通訊。 例如在 A 創建的時候發布某個訊息,B監聽到訊息之後修改他自己的某個屬性值。
使用 Command 模式將一些業務無關的功能從系統中抽離出來,例如郵件發送。
以上建議參考:Laravel wisper resque

4. 構想

以上的內容都避開了前後端需要聯調的測試案例,以下的內容主要是針對這塊。 Ruby 在這個方向已經有一些比較優雅的實現,有興趣的可以直接先去欣賞一下 Capybara。

隨著包括 Selenium Phantomjs 以及基於前者的 Watir 等一系列瀏覽器驅動的普及,使用程式碼控制瀏覽器已經不再是一件很複雜的事情。在這個能力的基礎上,可以嘗試把基於前端的測試分成四個步驟:

等待某标志性元素出现(例如等待页面载入玩,或者某个内容异步加载出现)
模拟用户操作,这里的操作包括且不局限于用户点击、用户输入
等待反馈中标志性元素出现(例如某某输入框出现)
判断内容,是否符合预期
基于这个流程,可以解决绝大多数的前端测试。但是单纯依靠这个流程任然不够,因为页面中可能出现例如验证码这样的阻碍元素,在不修改代码的前提下,可以尝试通过数据库/缓存来取到这些内容。同样,和测试接口相同,这里也涉及到在测试前数据库中插入测试数据,测试用例执行后严重数据库里面数据变化,以及全部测试完毕后删除测试数据的内容。最终导致这块测试用例代码的实现需要同时对前端后端有一定的了解。目前还在考虑在借鉴 Capybara 的基础上,设计出更加通用的方案。

最后贴一段 Capybara 的代码结束这段内容:

feature "Signing in" do
 background do
  User.make(:email => 'user@example.com', :password => 'caplin')
 end

 scenario "Signing in with correct credentials" do
  visit '/sessions/new'
  within("#session") do
   fill_in 'Email', :with => 'user@example.com'
   fill_in 'Password', :with => 'caplin'
  end
  click_button 'Sign in'
  expect(page).to have_content 'Success'
 end

 given(:other_user) { User.make(:email => 'other@example.com', :password => 'rous') }

 scenario "Signing in as another user" do
  visit '/sessions/new'
  within("#session") do
   fill_in 'Email', :with => other_user.email
   fill_in 'Password', :with => other_user.password
  end
  click_button 'Sign in'
  expect(page).to have_content 'Invalid email or password'
 end
end

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn