Leon's Blogging

Coding blogging for hackers.

用 Rspec + Factory Girl + CircleCi 寫測試

| Comments

程式寫久之後,就會發現測試的重要性! 因此來介紹 rails 中,比內建測試還好用的 rspec 搭配 factory_girl

目錄

RSpec

Ruby 的測試 DSL (Domain-specific language)

  • Semantic Code:⽐ Test::Unit 更好讀,寫的⼈ 更容易描述測試⺫的
  • Self-documenting:可執⾏的規格⽂件

測試種類

  • 單元測試(Unit test) 針對每個程式各個最小單位進行測試,像是在 controller 就單單只測試 controller 裡面的 action,而裡面產生的 model,method,都用假的方式來取代,已確保有錯誤時,可以很快知道是哪邊有問題 。

  • 整合測試(Integration test) 主要是用來測試,每個 class 的互動,像是 controller 裡面會 call 到 model ,也會 call 到 view ,並測試回傳的值是否正確。

寫測試的好處

  • Instant Feedback 即時反饋(寫測試的時間 < debug的時間)
  • 回歸測試及重構 (重構時就不需要再重複的測試)
  • 幫助設計API(TDD = 先測試,在實作)
  • 一種程式文件(可以讓很快就知道之前api怎麼寫的)

慣例

  • ⼀個 rb 檔案配⼀個同名的 spec.rb 檔案 (非常重要,如果檔名後面沒加 spec 就不會跑)
  • guard 等⼯具容易設定 guard-rspec 程式⼀修改完存檔,⾃動跑對應的測試(bundle後,輸入 guard init repec 初始化,打guard(bundle exec guard 真正執行))
  • editor 有⽀援快速鍵
  • describe “#name” 是 instance method
  • describe “.name” 是 class method
  • 測試spec盡量比較簡單清楚,可以不用DRY,實作才會要DRY

輸出格式

  • rspec filename.rb 預設不產⽣⽂件
  • rspec filename.rb -fd 輸出 specdoc ⽂件
  • rspec filename.rb -fh 輸出 html ⽂件

安裝

1
2
3
4
5
6
7
8
9
10
11
12
13
group :development, :test do
  gem 'rspec-rails'
  gem 'factory_girl_rails'
end

#ruby
gem install rspec
rspec --init
#create   .rspec
#create   spec/spec_helper.rb

#rails
rails generate rspec:install

設定

顏色描述

1
2
3
4
5
6
7
8
9
#vim .rspec檔案輸入
#Require spec_helper automatically in your *_spec.rb
--require spec_helper

#顯示顏色
--color

#顯示描述
--format documentation

將不需要的檔案關閉

generate 新的 controller 或是 model 時,rails 就會很聰明的順便新增 sepc 檔案,但有時候我們會希望用到的時候再去建立即可,所以需要關閉就輸入以下指令。

/config/application.rb

1
2
3
4
5
6
7
config.generators do |g|
  g.view_specs false
  g.helper_specs false
  g.request_specs false
  g.controller_specs false
  g.routing_specs false
end

語法介紹

  • describe 或 context 用來描述你要測試的是什麼,可以用nested
  • it 每個 it 就是⼀⼩段測試(it, specify, example都是一樣的)
  • expect(…).to 或 to_not 所有物件都有這個⽅法來定義你的期望
  • before(:each) 每段 it 之前執⾏
  • before(:all) 整段 describe 執⾏⼀次 *沒加(:xxx)預設是:each
  • after(:each) • afte(:all)
  • pending 可以先列出要測試的,但不用執行,直接在it前面加上x,xit
  • let(:name) { exp }
    • 相較於 before(:each) 可增加執⾏速度
    • 有使⽤到才會運算(lazy),並且在同⼀個 example 測試中多次呼叫會 Memoized 快取起來。
    • • let! 則是⾮ lazy 版本 (will create before every example)
  • 別名
    • describe = context
    • it = specify = example

Rspec

model

1
2
3
4
5
6
7
8
9
10
11
12
13
require 'rails_helper' #必須載入才能使用裡面的方法

RSpec.describe Post, type: :model do #RSpec 可省略
    it "is accessible" do
        post = Post.create!
        expect(post).to eq(Post.last)
    end

    it "has title and content columns" do
        columns = Post.column_names
        expect(columns).to include("id")
    end
end
  • describe, context 描述要測試的是什麼,可以用nested
  • it, specify, example 就是⼀⼩段測試
  • expect(…).toexpect(…).to_not 定義期望
  • eq 預期的是否和自己設定的相等
  • include 預期的是否有包括自己設定的值
  • describeit 前面加上 x 代表 pending,執行 rspec 就會先跳拓
  • 其他方法

Routing spec syntax

1
2
3
4
5
6
7
8
9
10
11
RSpec.describe "posts", :type => :routing do

  expect(:get => "/events").to route_to("events#index")
  expect(:get => "/widgets/1/edit").not_to be_routable

  expect(:get => "/posts/1").to route_to(
        :controller => "posts",
        :action => "show",
        :id => "1"
        )
end

Controller spec syntax

assigns 可以直接取得 instance 去測試

1
2
3
4
5
6
RSpec.describe PostsController, type: :controller do
  expect(response).to render_template(:new)
  expect(response).to redirect_to(events_url)
  expect(response).to have_http_status(200)
  expect(assigns(:event)).to be_a_new(Event)
end

View spec syntax

1
2
3
4
5
RSpec.describe "posts/index.html.erb", type: :view do
  render
  expect(rendered).to include("Title")
  expect(response).to render_template(partial: "_form")
end

Helper spec syntax

1
expect(helper.your_method).to eq("")

model spec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
describe Material::Banner, type: :model do
end

#驗證

it 'fails validation with no wrong video type' do
  video = Video.new(file: file)
  video.valid?
  expect(video.errors[:file].first).to include('allowed types: mp4, flv')
end

or

expect(video).to be_invalid

request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
RSpec.describe "Users", :type => :request do
  before do
    @user = User.create(name: "hello")
  end

  it "GET /users" do
    get "/users"
    expect(response).to have_http_status(200)
    expect(response).to render_template(:index)
    expect(response.body).to include("hello")
  end

  it "GET /user/:id" do
    get "/user", id: @user.id
    expect(response).to have_http_status(200)
    expect(response).to render_template(:index)
    expect(response.body).to include("hello")
  end

request 通常直接從網址進行 Get 或 Post ,接著判斷傳回來的值是否正確。

1
2
3
4
5
6
7
8
9
before do
  @user = User.new(name: "hello")
end

before(:all) do
  @user = User.new(name: "hello")
end

#也有 after(:each),afte(:all)
  • before(:each) 每段it之前執行
  • before(:all) 整段describe前只執行一次
  • after(:each) 每段it之後執行
  • after(:all) 整段describe後只執行一次
  • (:each) 可以不用加,預設為(:each)
1
let(:user){User.new(:name => "hello")}
  • 相較於 before(:each) 可增加執⾏速度
  • 有使⽤到才會運算(lazy),並且在同⼀個 example 測試中多次呼叫會 Memoized 快取起來。
  • let! 則是⾮ lazy 版本

預期會執行某一個 class 的 methd

1
 expect(Clsss).to receive(:method).with(params)

測試 Pattern

Four-Phase Test

  • Setup (準備測試資料)
  • Exercie (實際執行測試)
  • Verification (驗證測試結果)
  • Teardown (拆解測試)

double

假物件,可用於 mock 中指定回傳的值

mock 主要是用來模擬「外部邏輯」,因此可以使用 mock objects,在 RSpec 裡面叫做 double(替身)。

1
2
3
4
5
6
7
8
9
10
11
require 'rails_helper'

RSpec.describe BooksCalculator do
  describe "#books_count" do
    it "returns books count" do
      course = double(:books_count => 5 )
      book_calculator = StudentsCalculator.new(course)
      expect(book_calculator.books_count).to eq(5)
    end
  end
end

Stub(舊有方式,現已改為 Allow Message)

old-syntax/stub

Stub: For replacing a method with code that returns a specified result.

  • 用stub 假造 method,讓它忽略這個 method,或是指定回傳東西,可以避免在測試時,測試不必要的東西。
  • 專注於要測試的東西,如果 method 有呼叫其他 method 就可以 stub 掉
  • 製造假物件,指定這個假物件能回應哪些訊息,還有對應的回傳值,讓要測試的主角,在執行過程中一些地方,能獲得一致的結果

  • 要測試的 model

1
2
3
4
5
6
7
8
class Zombie < ActiveRecord::Base
  has_one :weapon

  def decapitate
    weapon.slice(self, :head)
    self.status = "dead again"
  end
end
  • 我们要測試 decapitate 方法, 但裡面調用了 weapon 的 slice 方法, 所以我們要把它 stub 掉
1
2
3
4
5
6
7
8
9
10
11
12
describe Zombie do
  let(:zombie) { Zombie.create }

  context "#decapitate" do
    it "sets status to dead again" do
      allow(zombie.weapon).to receive(:slice) #Rspec 3 之後的語法
      #zombie.weapon.stub(:slice)  #模擬 slice 阻斷掉他 Rspec 2
      zombie.decapitate
      zombie.status.should == "dead again"
    end
  end
end

Mock

Mock: A stub with an expectations that the method gets called.

  • 假造 method,不只忽略這個 method ,並且期望被呼叫到
  • 「模擬」與一個「協作者」的互動,設立一個「會收到指定訊息」的期望,去驗證互動是否真的有發生。
  • 簡單來說 mock 就是 stub + expectation , 說它是 stub 是因為它也可以像 stub 一樣偽造方法, 阻斷對原來方法的調用, expectation 是說它不僅偽造了這個方法,它還期望你(必須)調用這個方法,如果沒有被調用到,這個 test 就 fail 了
  • 一樣製造假物件,但是現在我們改對這個假物件斷言,在主角的執行過程中,應該要收到什麼訊息、收到的訊息應該夾帶什麼參數、訊息收到的次數…等等,但是會有程式碼的流程些微不自然的問題(準備、斷言、(準備)、執行)

使用 mock objects,在 RSpec 裡面叫做 double(替身),來取代「外部邏輯」資料

1
2
3
4
5
# simulate a not found resource
context "when not found" do
  before { allow(Resource).to receive(:where).with(created_from: params[:id]).and_return(false) }
  it { is_expected.to respond_with 404 }
end
1
2
3
4
5
6
7
8
9
10
describe Zombie do
  let(:zombie) { Zombie.create }

  context "#decapitate" do
    it "calls weapon.slice" do
      zombie.weapon.should_receive(:slice)
      zombie.decapitate
    end
  end
end
1
2
# 讓 User 這個 model 執行 follow 時都一律回傳 false, 以便測試到失敗的例子
allow_any_instance_of(User).to receive(:follow).and_return(false)

mock

範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#/app/models/zombie.rb
def geolocate
  loc = Zoogle.graveyard_locator(self.graveyard)
  "#{loc[:latitude]}, #{loc[:longitude]}"
end

#/spec/models/zombie_spec.rb
it "calls Zoogle.graveyard_locator" do
  Zoogle.should_receive(:graveyard_locator).with(zombie.graveyard)
    .and_return({latitude: 2, longitude: 3})
  zombie.geolocate
end
#stubs the method + expectation with correct param + return value

#/spec/models/zombie_spec.rb
it "returns properly formatted lat, long" do
  Zoogle.stub(:graveyard_locator).with(zombie.graveyard)
     .and_return({latitude: 2, longitude: 3})
  zombie.geolocate.should == "2, 3"
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#/app/models/zombie.rb
def geolocate_with_object
  loc = Zoogle.graveyard_locator(self.graveyard)
  "#{loc.latitude}, #{loc.longitude}"
end

def latitude
  return 2
end

def longitude
  return 3
end

#/spec/models/zombie_spec.rb
it "returns properly formatted lat, long" do
  loc = stub(latitude: 2, longitude: 3)
  Zoogle.stub(:graveyard_locator).returns(loc)
  zombie.geolocate_with_object.should == "2, 3"
end

Message Chains

可以一次 mock 多個 method

receive_message_chain

1
2
3
Http.get('http://google.com').parse

allow(HTTP).to receive_message_chain(:get, :parse).and_return({ 'data'=>[] })

Spies

  • 類似 mocks ,一樣製造假物件,一樣是對假物件斷言,但是透過測試工具的功能,而改善了測試程式碼的可讀性,流程更自然(準備、執行、斷言)

[RSpec] 進階測試系列概念 - Part 6 Mocking V.S. Spying

Stubs, Mocks and Spies,都是測試的技巧 or 手法!!

let & subject

1
2
3
4
#subject 是主要要測的物件
#let 則是測試中主要物件時,提供不同的條件
subject (:zombie) { Zombie.new(name:'john') }
let(:axe){ Weapon.new(name:'axe') }

subject

Subject blocks allow you to control the initialization of the subject under test. If you don’t have any custom initialization required, then you’re given a default subject method already. All it does is call new on the class you’re testing.

let

Let blocks allow you to provide some input to the subject block that change in various contexts. This way you can simply provide an alternative let block for a given value and not have to duplicate the setup code for the subject over again. Let blocks also work inside of before :each blocks if you need them.

let 只有在用到時才會執行
let! 每個測試前都會執行

1
2
3
4
5
6
7
describe User do
  subject(:user){ described_class.new firstname:firstname } #described_class = User
  
  context '' do
    let(:firstname) {'test'}
  end
end

subject One-liner syntax

1
2
3
4
5
6
7
8
RSpec.describe Array do
  describe "with 3 items" do
    subject { [1,2,3] }
    it { should_not be_empty }
    # or
    it { is_expected.not_to be_empty }
  end
end

參考文件:

focus

當想要只跑指定的測試時,可以加上 focus

1
2
3
4
  it '#index',  focus:true do
    get :index, format: :json
    expect(response).to have_http_status 200
  end
1
rspec --tag focus

要忽略特定的測試,可以加上 slow

1
2
3
4
  it '#index',  slow:true do
    get :index, format: :json
    expect(response).to have_http_status 200
  end

可以不就在輸入 tag 就會執行

1
2
3
4
5
6
7
8
#spec/spec_helper.rb
RSpec.configure do |config|
  config.filter_run focus: true
  config.run_all_with_everything_filtered = true

  config.filter_run_excluding slow: true
  config.run_all_with_everything_filtered = true
end

matchers

include

可以將常用的包成 method include 進來。

spec/custom_helper.rb

1
2
3
4
5
module CustomHelper
  def my_helper_method(success)
    ...
  end
end

要記得 reqyire & include 進來

spec/rails_helper.rb

1
2
3
4
5
6
7
8
require_relative 'custom_helper'

RSpec.configure do |config|
  FactoryGirl::SyntaxRunner.send(:include, CustomHelper)
  #factory girl 也可以引入這個 helper 進來

  config.include CustomHelper
end

factories/post.rb

1
2
3
4
5
FactoryGirl.define do
  factory :post do
    title { my_helper_method(attr) } #記得要加 {}
  end
end

Define helper methods in a module

shared_examples_for

1
2
3
4
5
6
7
8
9
10
11
describe Vampire do
   it_behaves_like 'the undead'
   #let(:undead) { Zombie.new }
end

shared_examples_for 'the undead' do  #RSpec.shared_examples
  it 'does not have a pulse' do
    subject.pulse.should == false
    #undead.pulse.should == false
  end
end
1
2
3
4
5
6
7
8
9
10
#spec/models/vampire_spec.rb
describe Zombie do
  it_behaves_like 'the undead', Zombie.new
end

#spec/support/shared_examples_for_undead.rb
shared_examples_for 'the undead' do |undead|
  it 'does not have a pulse' do
undead.pulse.should == false end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
describe '#fullname' do
  shared_example_for 'a fullname' do |(first, middle, last), output|
    subject(fullname){ User.fullname }

    let(:firstname) {first}
    let(:middlename) {middle}
    let(:lastname)   {last}
  
    it { is_expected.to eq output }
  end
end

  {
  [nil, 'two', 'three'] => 'two three',
  [one, 'two', 'three'] => 'one two three',
  [nil, 'two', nil]     => 'two',
  [one, 'nil', 'three'] => 'one three',
  }.each do |name_set, output|
    it_behaves_like 'a fullname', name_set, output
  end
end

參考文件:

custom matcher

1
2
3
4
5
6
7
8
#spec/models/zombie_spec.rb

describe Zombie do
  it 'validates presence of name' do
    zombie = Zombie.new(name: nil)
    zombie.should validate_presence_of(:name)
  end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#spec/support/validate_presence_of.rb
 module ValidatePresenceOf
   class Matcher
     def initialize(attribute)
       @attribute = attribute
       #default message
       @message = "can't be blank"
     end

     def matches?(model)
       @model = model
       #主要的測試
       model.valid?
       model.errors.has_key?(@attribute)
       #collect errors and find a match
       errors = @model.errors[@attribute]
       errors.any? { |error| error == @message }
     end
   end
   def validate_presence_of(attribute)
     Matcher.new(attribute)
   end

   def failure_message
      "#{@model.class} failed to validate :#{@attribute} presence."
   end

   def negative_failure_message
      "#{@model.class} validated :#{@attribute} presence."
   end

   #override failure message & return self
   def with_message(message)
     @message = message
     self
   end
end
1
2
3
4
5
6
7
8
9
10
11
require 'rspec/expectations'

RSpec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    actual % expected == 0
  emd
end

RSpec.describe 9 do
  it { is_expeced.to be_a_multiple_of(3)}
end

expected_to

  • is_expected is defined simply as expect(subject) and is designed for when you are using rspec-expectations with its newer expect-based syntax.

One-liner syntax

RSpec Testing for a JSON API

RSpec Testing for a JSON API
Testing Rails jBuilder JSON APIs With RSpec

CURL

可以用 command line 來測試 get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
curl -i localhost:3000/posts
#-i 詳細資訊

curl -IH "Accept: application/json" localhost:3000/posts
curl -IH "Accept: application/xml" localhost:3000/posts
#H HEAD

curl -i -X POST -d 'episode[title]=ZombieApocalypseNow' http://localhost:3000/posts

#-X the -X option specifies the method
#-d use -d to send data on the request

curl -Iu 'carlos:secret' http://localhost:3000/posts
#上下一樣
curl -I http://carlos:secret@localhost:3000/episodes
#send Basic Auth credentials with the -u option

curl -IH "Accept: application/json" -u 'carlos:fakesecret' http://localhost:3000/posts
#client asks for JSON

curl -IH "Authorization: Token token=16d7d6089b8fe0c5e19bfe10bb156832" http://localhost:3000/posts
#Set token on Authorization header

Factory Bot (前身Factory Girl)

spec/rails_helper.rb 設定

以下 FactoryGirl 要改成 FactoryBot :: 2018-06-29

1
2
3
RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end

設定好後原本需要

1
FactoryGirl.create(:user)

就不用加前面的 FactoryGirl 直接

1
create(:user)

spec 底下新增 factories 資料夾,接著在裡面新增相對應的物件名稱,像是 user.rb

spec/factories/user.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
FactoryGirl.define do
  factory :user, class: User" do
    name "hello"
    age  18

    # 可以設定create 之後要做哪些動作
    after(:create) do |video|
      create(:photo, photo_id: photo.id)
      create(:photo, size: "500", photo_id: photo.id)
      create(:photo, size: "800", photo_id: photo.id)
    end

    # 也可以設定多種條件
    # 呼叫方式 create(:user, :child)
    trait :child do
      age 6
      #after(:create) {|user| user.add_role(:admin) }
      #after(:build)  {|user| user.add_role(:admin) }
      # 也可以設定 create 之後的設定
    end
    
    # 可以再接著 user 去做更改,跟 trait 不一樣的是,trait 比較像是組合的概念一個一個加上去,factory 就是完整的
    # 呼叫方式 create(:user_has_books)
    factory :user_has_books do
     name :user_b
    end
  end
end

這樣在 spec 裡面就可以直接建立假資料

1
2
3
4
before do
  @user  = FactoryGirl.create(:user) #FactoryGirl 可省略
  @child = create(:user, :child) #就只替換掉 age
end

transient, evaluator

允許傳入不存在 model 的 data,再透過 evaluator callback 去取得!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FactoryBot.define do
  factory :user do
    name { 'leon' }

    factory :user_with_book do
      transient do
        books { nil }
      end

      after(:create) do |user, evaluator|
        user.update(books: evaluator.books || [
          books.new(name: 'This is book')
        ])
      end
    end
  end
end
1
2
3
4
5
6
7
8
9
10
11
12
let(:user_with_book) do
  create(:user_with_book, {
    books: books
  })
end

let(:books) do
  [
    create(:books, name: 'book 1'),
    create(:books, name: 'book 2'),
  ]
end

sequences

可以已 auto incremental 的方式產生資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FactoryBot.define do
  factory :user do
    first_name "John"
    last_name "doe"

    # 第一個參數是欄位名稱,第二個參數則是起始的數值。
    sequence(:id_number, '01') { |n| "E12223334#{n}" }
    # 可利用 cycle 讓數字做循環
    sequence(:id_number, ('01'..'99').cycle) { |n| "E12223334#{n}" }

    # 會重複出現一樣的公司名稱
    company_name Faker::Company.name
    # 無論產生幾筆資料都是隨機的
    company_name { Faker::Company.name }
  end
end

注意

factory_girl 產生出來的資料,不會透過 controller ,而是直接再 model 產生,因此會跑出 validation 的驗證。

若是希望能跑 controller action 裡的 method 則是要另外跑

1
2
3
4
5
trait :user_buy do
  after(:create) do |user|
    user.buy
  end
end

為什麼要假物件?

  • 無法控制回傳值的外部系統 (例如第三⽅ web service)
  • 建構正確的回傳值很⿇煩 (例如得準備很多假資料)
  • 可能很慢,拖慢測試速度 (例如耗時的運算)
  • 有難以預測的回傳值 (例如亂數⽅法)
  • 還沒開始實作 (特別是採⽤ TDD 流程)

Faker

可搭配 Faker 用來產生假資料

其他設定

rails g model 時,一併產生 factory_girl 的檔案在 spc/factories

1
2
3
4
config.generators do |g|
  g.test_framework :rspec, :fixture => true, :views => false, :fixture_replacement => :factory_girl
  g.fixture_replacement :factory_girl, :dir => "spec/factories"
end

Database Cleaner

參考文件:

Capybara

RSpec除了可以拿來寫單元程式,我們也可以把測試的層級拉高做整合性測試,以Web應用程式來說,就是去自動化瀏覽器的操作,實際去向網站伺服器請求,然後驗證出來的HTML是正確的輸出。

capybara就是一套可以搭配的工具,用來模擬瀏覽器行為

如果真的需要打開瀏覽器測試,例如需要測試JavaScript和Ajax介面,可以使用 seleniumhq Watir 工具

RSpec & Capybara 整合測試(Selenium and Poltergeist Driver)

CI server

CI(Continuous Integration) 伺服器的用處是每次有人Commit就會自動執行編譯及測試(Ruby不用編譯,所以主要的用處是跑測試),並回報結果,如果有人送交的程式搞砸了回歸測試,馬上就有回饋可以知道。

circleci.com

建立 circle.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
machine:
  timezone:
    Asia/Taipei
  ruby:
    version: 2.1.2
dependencies:
  pre:
    - rvm use 2.1.2
    - gem install bundler
    - gem install rubocop
  post:
    - gem update rake
database:
  override:
    - cp config/database.yml.example config/database.yml
    - rake db:create db:migrate --trace
test:
  override:
    - bundle exec rspec --color

建立 config/database.yml.example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
default: &default
  adapter: mysql2
  encoding: utf8
  host: localhost
  username:
  password:

test:
  <<: *default
  database: test_db

development:
  <<: *default
  database: development_db

production:
  <<: *default
  database: production_db

接著到 circleci.com 和 github 帳號做連結。 接著將要跑的 project 加進去,之後只要 push 到 github 就會自動跑了!

Other

guard-rspec
Continuous Testing 的工具。程式修改完存檔,自動跑對應的測試。

shoulda-matchers
提供了更多Rails的專屬Matchers

SimpleCov
用來顯示,測試涵蓋的範圍,可知道哪些地方沒有測試到

大師引言

1
2
3
4
5
6
7
8
大部份都是寫model的測試
controller偶爾會寫

其他的因為後面有驗收測試也會測試到,所以不浪費時間去寫測試
驗收測試多半是測試子路徑,不會測到所有的條件,所以個別的小項測試,就直接在model寫就好了

工程是寫到request就很棒了
feature 比較像是QA在寫的

官方文件:

Gem:

參考文件:

RailsPacific:

Rspec-Style-Guide:

Comments