Nodejs學習筆記之測試驅動
分享第二章,關于測試驅動。這里的測試主要針對Web后端的測試 —— 你為什么要寫測試用例(即測試用例的完善是否是浪費時間),如何完善你的測試用例,代碼設計如何簡化測試用例的書寫,以及一些后期的構想。
1. 你為什么要寫測試用例
這個習慣通常會被認為是一種耽誤開發(fā)進度的行為,你需要花費幾乎和開發(fā)代碼相同的時間來逐步完善你的測試用例。但是在開發(fā)過程中,在開發(fā)完成一段代碼后如果負責任而不是說完全把問題交給測試人員去發(fā)現(xiàn)的話,這個時候通常都會去做一些手動的測試。例如:
在代碼中執(zhí)行某些方法,查看輸出的值是否符合預期。
修改數(shù)據(jù)庫/緩存,然后執(zhí)行某些方法,看數(shù)據(jù)庫的變化是否符合預期。
使用工具模擬請求某些接口,查看接口的返回值/數(shù)據(jù)庫的變化值是否會符合預期。
如果有前端頁面的話,還會涉及到前后端聯(lián)調,即要在前端頁面上通過前端交互,查看前端的反饋是否符合預期,來間接驗證后端代碼的正確性。
現(xiàn)代化的測試工具都在盡可能的將這些人工的手動測試行為抽象成代碼塊,當你有意識去進行手動測試的時候,其實已經(jīng)開始在嘗試測試用例的行為了。既然可以通過手動的方式進行測試,那為什么還需要用代碼來實現(xiàn)測試?
代碼是可以復用或者在簡單重構后可以實現(xiàn)更多的功能的,但是當你選擇手動的時候,每次你都需要重頭開始。
成熟的工作流中應當包括代碼審核流程,代碼審核的方式有很多,逐句閱讀你的代碼,或者檢查你測試代碼的完善性以及正確性,然后運行你的測試用例。后者更加簡單。
當代碼改動,例如修復 Bug 時候,很難保證你的改動是否會影響其他依賴你代碼的部分。在人工測試的時代有一個叫做回歸測試,即在你修復 Bug 將你的系統(tǒng)重新測試一遍。但是如果你已經(jīng)有了完善的測試用例了呢,直接執(zhí)行命令搞定。
當你重構代碼的時候,同上。
2. 如何完善你的測試用例
在進入完善階段前,先說說你將如何實現(xiàn)測試用例。
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 包含的代碼塊是在執(zhí)行下面的測試用例前要做的事情,通常還會支持一個相對應的方法,在測試用例執(zhí)行完執(zhí)行。每個用例里面都進行一些很小的判斷。
第一段中提到了一些手動測試里面經(jīng)常會涉及到的測試內(nèi)容,這里拿其中的 2 和 3 進行說明。在進行數(shù)據(jù)庫相關的測試時,需要在 before 中插入一條測試數(shù)據(jù),并且在 after 中刪除測試數(shù)據(jù)。中間的測試用例中,通過執(zhí)行相應的方法,執(zhí)行完畢后:檢查數(shù)據(jù)變化情況/檢查是否有預期的異常/是否返回預期結果 來確認代碼的正確性。如果是接口的話,就是通過代碼發(fā)起對應的請求,然后檢查返回的內(nèi)容是否返回預期,有需要的話再去查看數(shù)據(jù)庫里面的數(shù)據(jù)是否符合預期變化。
現(xiàn)在已經(jīng)有了測試用例,但是任然需要考慮一種特殊情況。我現(xiàn)在為一個函數(shù)寫了相對完善的測試用例了,跑完都 PASS 了,結果發(fā)現(xiàn)線上的日志里面還是有那個函數(shù)的報錯。檢查下發(fā)現(xiàn)函數(shù)的某個分支之前在測試的時候沒有測試到,剛好線上的某種情況運行到了這個分支,結果有一個很不明顯的語法錯誤報錯了,有沒有辦法能確保所有的代碼都測試過了?這里需要引入的是一個叫做 測試用例覆蓋率 的概念,基本上每個語言都會有響應的實現(xiàn)。通過測試用例覆蓋率,量化的告訴你你的測試用例有沒有跑完某某文件里的所有代碼,而你需要做的,就是盡可能保證你的覆蓋率保持在 100%。
某種意義上來說,測試用例和測試覆蓋率是用來提高開發(fā)者對自己代碼自信心的工具。但是,他們也不是萬能的。測試用例里面總可能會漏掉一些參數(shù)的可能性,當然你的代碼里面也沒有為這種可能性進行代碼的編寫,最終測試用例覆蓋率只能告訴你你寫的代碼我們都幫你檢測過了測試過了,對于你沒有考慮到的可能性,表示無能為力。所以盡可能編寫嚴格的代碼,例如 javascript 里面盡可能都用 === 而不是 ==,使用強類型的編程規(guī)范等等,這些來降低這種因為接受的參數(shù)范圍過大帶來的潛在風險。
3. 代碼設計如何簡化測試用例的書寫
整個 Web (也不局限于 web)通常包括三個層面的代碼 —— 單純數(shù)據(jù)處理與運算、涉及到數(shù)據(jù)庫、涉及到具體的網(wǎng)絡協(xié)議。其中單純的數(shù)據(jù)運算于處理主要為普通的運算的函數(shù)或者是其他代碼,涉及到數(shù)據(jù)庫就是傳統(tǒng)意義上 MVC 里面的 M,涉及到具體的網(wǎng)絡協(xié)議就是對應的 C。這三塊的測試分別對應著第一節(jié)中常規(guī)的測試內(nèi)容的前三條。
因為 C層面通常還可能涉及到頁面的渲染以及相應協(xié)議的模擬,所以通常把測試的重心放在函數(shù)以及數(shù)據(jù)庫相關的代碼里面可以減少測試用例代碼的復雜度,這個就要求 Controller 的代碼要盡可能少。對于復雜度較高的應用的一些目前的一些建議:
將數(shù)據(jù)的基礎校驗都放在 M層,如果使用 Ruby 開發(fā)的話,ActiveRecord以及Mongoid都提供了很方便使用的 validation 功能。
嘗試在代碼中使用 Pub/Sub 模式配合一些 ORM中提供的鉤子(hook) 來實現(xiàn) Model 之間的通信。 例如在 A 創(chuàng)建的時候發(fā)布某個消息,B監(jiān)聽到消息之后修改他自己的某個屬性值。
使用 Command 模式將一些業(yè)務無關的功能從系統(tǒng)中抽離出來,例如郵件發(fā)送。
以上建議參考:Laravel wisper resque
4. 構想
以上的內(nèi)容都避開了前后端需要聯(lián)調的測試用例,下面的內(nèi)容主要是針對這塊。Ruby 在這個方向已經(jīng)有一些比較優(yōu)雅的實現(xiàn),感興趣的可以直接先去欣賞一下 Capybara。
隨著包括 Selenium Phantomjs 以及基于前者的 Watir 等一系列瀏覽器驅動的普及,使用代碼控制瀏覽器已經(jīng)不再是一件很復雜的事情。在這個能力的基礎上,可以嘗試把基于前端的測試分為四步:
等待某標志性元素出現(xiàn)(例如等待頁面載入玩,或者某個內(nèi)容異步加載出現(xiàn))
模擬用戶操作,這里的操作包括且不局限于用戶點擊、用戶輸入
等待反饋中標志性元素出現(xiàn)(例如某某輸入框出現(xiàn))
判斷內(nèi)容,是否符合預期
基于這個流程,可以解決絕大多數(shù)的前端測試。但是單純依靠這個流程任然不夠,因為頁面中可能出現(xiàn)例如驗證碼這樣的阻礙元素,在不修改代碼的前提下,可以嘗試通過數(shù)據(jù)庫/緩存來取到這些內(nèi)容。同樣,和測試接口相同,這里也涉及到在測試前數(shù)據(jù)庫中插入測試數(shù)據(jù),測試用例執(zhí)行后嚴重數(shù)據(jù)庫里面數(shù)據(jù)變化,以及全部測試完畢后刪除測試數(shù)據(jù)的內(nèi)容。最終導致這塊測試用例代碼的實現(xiàn)需要同時對前端后端有一定的了解。目前還在考慮在借鑒 Capybara 的基礎上,設計出更加通用的方案。
最后貼一段 Capybara 的代碼結束這段內(nèi)容:
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
相關文章
使用?Node-RED對?MQTT?數(shù)據(jù)流處理
本文將介紹使用 Node-RED 連接到 MQTT 服務器,并對 MQTT 數(shù)據(jù)進行過濾和處理后再將其發(fā)送至 MQTT 服務器的完整操作流程。讀者可以快速了解如何使用 Node-RED 對 MQTT 數(shù)據(jù)進行簡單的流處理2022-05-05詳解如何使用koa實現(xiàn)socket.io官網(wǎng)的例子
這篇文章主要介紹了詳解如何使用koa實現(xiàn)socket.io官網(wǎng)的例子,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11pm2發(fā)布node配置文件ecosystem.json詳解
這篇文章主要介紹了pm2發(fā)布node配置文件ecosystem.json詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05Node.js設置定時任務之node-schedule模塊的使用詳解
node-schedule是 Node.js 的一個定時任務(crontab)模塊。這篇文章主要介紹了Node.js設置定時任務之node-schedule模塊的使用,需要的朋友可以參考下2020-04-04nodejs對mongodb數(shù)據(jù)庫的增加修刪該查實例代碼
在本篇文章里小編給大家整理的是一篇關于nodejs對mongodb數(shù)據(jù)庫的增加修刪該查實例代碼,有需要的朋友們可以參考下。2020-01-01