我經常被問到,怎樣開始測試 Rails 程序。其實最爲一個測試新手,最可貴地方在於你不知道一些專業術語或者該問怎樣的問題。下面所寫的是一些概覽,關於咱們使用什麼工具,爲何使用這些工具,和一些須要牢記在心的建議。css
咱們用 RSpec 而不是 Test::Unit,是由於語法更友好,可讀性更高。固然,你能夠花幾天時間爭論到底該用哪一個框架,每一個框架都有本身的優勢。最主要的是你在用他們進行測試。html
Feature specs,能夠測試你整個程序的高級測試工具,保證每一個部件都工做正常,挺讚的。他是從用戶的角度編寫的,好比用戶點擊或者填寫表單。咱們用 RSpec 和 Capybara,他們容許你寫照這樣編寫能夠和網頁進行交互的測試。git
這是一個 RSpec feature 測試的栗子:github
# spec/features/user_creates_a_foobar_spec.rb feature 'User creates a foobar' do scenario 'they see the foobar on the page' do visit new_foobar_path fill_in 'Name', with: 'My foobar' click_button 'Create Foobar' expect(page).to have_css '.foobar-name', 'My foobar' end end
這個測試,模擬了一個用戶打開新建foobar
的表單,填入信息,點擊「Create」。這個測試而後假設頁面正如期待,正確地顯示剛纔建立的foobar
。web
這些對於測試高級功能來講很棒,可是記住,feature specs 跑起來很慢。在用 Capybara 測試應用全部的可能路徑的時候,把測試的邊緣狀況留模型,視圖,控制器的 sepecs。數據庫
我傾向於關於怎麼區分 Rspec 和 Capybara 方法的不一樣點。Capybara 方法其實是和頁面進行交互,好比點擊,表單操做,或者在頁面上查找元素。你能夠在 Capybara 的finders,matchers,和 actions 查看更多文檔。瀏覽器
Model specs 常常被用來測試系統中較小的部件,好比類或方法,這點和單元測試挺類似的。有時,他們也會和數據庫進行交互。他們運行起來很快,也能夠處理正在測試系統(system under test)中的一些邊緣狀況。ruby
在 RSpec 中,他們看起來像這樣:網絡
# spec/models/user_spec.rb # Prefix class methods with a '.' describe User, '.active' do it 'returns only active users' do # setup active_user = create(:user, active: true) non_active_user = create(:user, active: false) # exercise result = User.active # verify expect(result).to eq [active_user] # teardown is handled for you by RSpec end end # Prefix instance methods with a '#' describe User, '#name' do it 'returns the concatenated first and last name' do # setup user = build(:user, first_name: 'Josh', last_name: 'Steiner') # excercise and verify expect(user.name).to eq 'Josh Steiner' end end
爲了維護的可讀性,確保你寫的測試是 Four Phase Testsession
The Four-Phase Test is a testing pattern, applicable to all programming languages and unit tests (not so much integration tests).
It takes the following general form:
test do setup exercise verify teardown end
好比:
it 'encrypts the password' do user = User.new(password: 'password') user.save user.encrypted_password.should_not be_nil end
當經過一個控制器測試多個路徑的時候,咱們喜歡用 controller specs 而不是 feature specs,由於他很快,並且寫起來容易。
一個好的測試驗證的 use case:
# spec/controllers/sessions_controller_spec.rb describe 'POST #create' do context 'when password is invalid' do it 'renders the page with error' do user = create(:user) post :create, session: { email: user.email, password: 'invalid' } expect(response).to render_template(:new) expect(flash[:notice]).to match(/^Email and password do not match/) end end context 'when password is valid' do it 'sets the user in the session and redirects them to their dashboard' do user = create(:user) post :create, session: { email: user.email, password: user.password } expect(response).to redirect_to '/dashboard' expect(controller.current_user).to eq user end end end
View specs 對於測試在模板中根據條件顯示不一樣信息來講很是有用,可是不少開發者卻忘了這個,而用 feature specs。而後絞盡腦汁爲何他們花了那麼長時間跑測試。固然,你能夠用 feature spec 覆蓋每個條件視圖,但我更喜歡像這樣使用 view specs :
# spec/views/products/_product.html.erb_spec.rb describe 'products/_product.html.erb' do context 'when the product has a url' do it 'displays the url' do assign(:product, build(:product, url: 'http://example.com') render expect(rendered).to have_link 'Product', href: 'http://example.com' end end context 'when the product url is nil' do it "displays 'None'" do assign(:product, build(:product, url: nil) render expect(rendered).to have_content 'None' end end end
當編寫測試代碼的時候,你會須要在不一樣場景往數據庫裏灌數據。你可使用內建的User.create
,可是當 model 中有不少 validations 時候,這樣好乏味啊。經過User.create
,即使你的測試代碼和這些驗證無關,你也不得不指定屬性來知足 validations。最重要的是,若是後來你修改了 validations,你還要從新修改測試套件的代碼。解決方案就是用 factories(工廠,數據生成器) 或者 fixtures(夾具)來建立模型。
咱們更喜歡 factories(和 FactoryGirl)賽過 Rails 的 fixgures,由於 fixtures 是神祕嘉賓。夾具讓人很難看到內因和效果,由於部分邏輯在你使用它的時候已經在文件中被定義好了。由於夾具遠在在測試以前就已經被生成,他們變得很難控制。
Factories,另外一方面來講,把邏輯正確地放到測試中。這讓咱們很容易地看到正在發生什麼,並且對於不一樣的場景更加靈活。Factories 比夾具更慢,可是從靈活性和可讀性上來講,這點犧牲是值得的!
把數據固化到數據庫也會減慢測試速度。不管合適,優先使用 FactoryGirl 的 build_stubbed
,而不是create
。build_stubbed
會在內存中生成,避免寫入磁盤。若是你測試一些查詢操做(User.where(admin: true)
),你會但願從數據庫裏進行查找,這就意味着你必須使用create
。
你會最終碰到一種場景,你須要測試一些依賴 JavaScript 代碼的功能。用默認的驅動跑 specs 不會執行頁面中任何 JavaScript 代碼。
你須要兩個法器,來跑帶有 JavaScript 代碼的功能 specs
有兩種類型的 JavaScript 驅動。好比像 Selenium,它會打開一個 GUI 窗口瀏覽器,而後在你看着它的時候,在頁面上點擊。這是一個頗有用的工具在測試中用來視圖化。可是不幸的是,啓動一整個 GUI 窗口瀏覽器很慢。因爲這個緣由,咱們傾向於使用 headless 瀏覽器。對於 Rails 來講,你可能會使用 Poltergeist 或者 [Capybara] Webkit(https://github.com/thoughtbot/capybara-webkit)。
feature 'User creates a foobar' do scenario 'they see the foobar on the page', js: true do ... end end
用合適的關鍵字,RSpec 會根據須要運行任何 JavaScript。
默認狀況下,跑 Rails 測試的時候,Rails 會把每一個場景都存在數據庫事務中。這就說明,在每一個測試結束的時候,Rails 會回滾全部在測試中的修改。這點很是好,由於咱們不想任何測試數據會對別的測試產生反作用。
不幸的是,當咱們使用 JavaScript 驅動的時候,這個測試實在另外一個線程進行的。這就意味着,它並無和程序共用同一個數據庫鏈接,並且爲了運行程序看到測試結果,你的測試會提交這個事務。爲了解決這個問題,咱們能夠容許數據庫提交這些數據,而後接着,在每一個 spec 後 Truncate
數據庫。這樣會比事務慢一點,因此,咱們只在須要的時候使用 truncation
。
這就是 Database Cleaner 的用處。Database Cleaner 容許你配置策略。我建議閱讀下 Avdi 的文章 看看血淋淋的細節。這是個極(喪)其(心)詳(病)細(狂)的配置過程,因此我一般在項目中來回拷貝這個文件,或者使用 Suspenders,這樣就能夠輕易搞定了。
double 方法能夠模擬系統中另一個對象。常常的,你會須要一個替身,而且只測試一個屬性,因此不值得加載整個 ActiveRecord 對象。
car = double(:car)
當你使用 stubs,你是在告訴一個對象去響應一個已給出的方法。若是 stub 以前的 double
car.stub(:max_speed).and_return(120)
咱們如今能夠期待當訪問 max_speed
咱們的 car
對象老是返回120
。臨時產生能夠響應一個方法的對象並且帶有一樣的依賴關係,而不用系統中真的存在的對象,這是很棒的方法!在這個栗子中,咱們在一個 double 出來的對象上進行了 stub,實際上你還能夠在別的對象 stub 任何方法。
咱們能夠把上邊的代碼簡化爲這樣:
car = double(:car, max_spped: 120)
當測試你的程序時,你會碰到你想驗證當一個對象接收到一個指定的方法的場景。爲了遵守 Four Phase Test 的最佳實踐,咱們用測試間諜,這樣咱們的期待會進入最佳的 verify
狀態。以前,咱們用 Bourne 作到這點,可是 RSpec 已經在 RSpec Mocks 中包括了這項功能。看看文檔中的這個栗子:
invitation = double('invitation', accept: true) user.accept_invitation(invitation) expect(invitation).to have_received(:accept)
基於三方服務的測試套件跑起來很慢的,會由於網絡鏈接的斷開致使失敗,並且也可能會由於服務頻率限制或者缺乏沙盒環境致使失敗。
確保你的測試套件在用 Webmock stub 外部請求的時候,不會和三方服務交互。這個能夠配置在 spec/spec_helper.rb
:
require 'webmock/rspec' WebMock.disable_net_connect!(allow_localhost: true)
避免使用三方請求,學習怎樣 stub 外部服務請求。
這僅僅是個如何開始測試 Rails 應用的概覽。爲了促進學習,我很是推薦你上咱們的 TDD workshop,在這裏,你能夠經過從零開始創建兩個 Rails 應用,來深度學這些課程。課程覆蓋到重構,爲確保應用和測試代碼的可維護性。TDDworkshop 的學生也能夠介入咱們的工做時間,你實時地能夠問咱們攻城獅任何問題。
當我是新手的時候我學習了這個課程,我牆裂推薦!