셋업
vi Gemfile; bundle install

group :test do
  gem 'mocha'
  gem 'capybara'
end
vi test/test_helper.rb

require 'minitest/pride'
require 'mocha/mini_test'
require "capybara/rails"
class ActionDispatch::IntegrationTest
  include Capybara::DSL
end
여기서 minitest/pride는 컬러(colorized) 출력 기능을 제공하고, include Capybara::DSL은 Integration 테스팅 시 capybara를 사용할 수 있게 한다.
rails g zombie name graveyard
rails g integration_test zombies
# test/ 디렉토리 및 파일
test
├── controllers
│   └── zombies_controller_test.rb
├── fixtures
│   └── zombies.yml
├── helpers
├── integration
│   └── zombies_test.rb
├── mailers
├── models
│   └── zombie_test.rb
└── test_helper.rb
rake test

rake test
rake test:units
rake test:functionals
rake test:integration
동작 확인: "the truth" 테스트
vi test/models/zombie_test.rb

class ZombieTest < ActiveSupport::TestCase
  test "the truth" do
    assert true
  end
end
test 블럭에 주어지는 assert()의 인자가 참(=true)이면 테스트가 통과한다. MiniTest::Unit 에서 사용할 수 있는 assert 문의 종류를 알고 싶으면 콘솔에서 아래 명령을 수행한다.
Minitest::Unit::TestCase.new(nil).methods.grep(/assert/)
refute()를 통해서 거짓(=false)에 대해 패스하는 네거티브 테스트를 수행한다.
Minitest::Unit::TestCase.new(nil).methods.grep(/refute/)
"invalid without a name" 테스트
vi test/models/zombie_test.rb

test "invalid without a name" do
  # z = Zombie.new
  z = zombies(:ash)
  z.name = nil
  assert !z.valid?, "Name is not being validated"
end
vi test/fixtures/zombies.yml

ash:
  name: Ash
  graveyard: Oak Park
vi app/models/zombie.rb
validates :name, presence: true
"valid with all attributes" 테스트
vi test/models/zombie_test.rb

test "invalid without a name" do
  z = zombies(:ash)
  assert z.valid?, 'Zombie is valid'
end
"invalid name gives error message" 테스트
vi test/models/zombie_test.rb

test "invalid name gives error message" do
  z = zombies(:ash)
  z.name = nil
  z.valid?
  assert_match /can't be blank/, z.errors[:name].join, "Presence error not found on zombie"
end
"can generate avatar_url" 테스트
vi test/models/zombie_test.rb

test "can generate avatar_url" do
  z = zombies(:ash)
  assert_equal "http://zombitar.com/#{z.id}.jpg", z.avatar_url
end
vi app/models/zombie.rb

def avatar_url
  "http://zombitar.com/#{id}.jpg"
end
"should respond to tweets" 테스트
vi test/models/zombie_test.rb

test "should respond to tweets" do
  z = zombies(:ash)
  assert_respond_to z, :tweets
end
vi app/models/zombie.rb
has_many :tweets
"should contain tweets" 테스트
vi test/models/zombie_test.rb

test "should contain tweets" do
  z = zombies(:ash)
  assert_includes z.tweets, tweets(:ash_first_tweet)
  assert_includes z.tweets, tweets(:ash_second_tweet)
  assert_equal z.tweets.order("id desc").map(&:id), [tweets(:ash_first_tweet).id, tweets(:ash_second_tweet).id]
  assert z.tweets.all? {|t| t.zombie == z }
end
rails g model tweet status zombie:references
vi test/fixtures/tweets.yml

ash_first_tweet:
  status: Bite
  zombie: ash

ash_second_tweet:
  status: 'I want Blood'
  zombie: ash
예전의 fixture와 달리 모델간 association 셋업이 간단해졌다.
setup()과 teardown() 사용하기
vi test/models/zombie_test.rb

def setup
  @zombie = zombies(:ash)
end
setup이 정의되어 있으면 매 test 시작 전에 이 메쏘드가 실행된다. 반대로 teardown은 매 test 종료 후 실행되는 콜백으로 동작한다.
"decapitate should set status to dead again" 테스트
vi test/models/zombie_test.rb

test "decapitate should set status to dead again" do
  @zombie.weapon.stubs(:slice)
  @zombie.decapitate
  assert "dead again", @zombie.status
end
rails g migration add_status_to_zombies status:string
rails g model weapon zombie:references
vi app/models/zombie.rb

has_one :weapon

def decapitate
  weapon.slice(self, :head)
  self.status = "dead again"
end
vi test/fixtures/weapon.yml

zombie_weapon:
  zombie: ash
이 시험의 목적은 Zombie#decapitate가 호출된 후 @zombie.status에 원하는 값이 설정되었는지를 테스트하는 것이다. 만약 decapitate() 내에서 호출하는 Weapon#slice()가 많은 시간을 잡아먹는 소위 expensive 메쏘드라고 가정해보면 굳이 시험 결과에 영향을 미치지 않는 이 메쏘드를 실행하느라 전체적인 시험 시간이 늘어지게 된다. 이런 경우 stubs(:slice)를 사용하면 Weapon#slice를 건너뛰고 시험을 수행할 수 있다.
"decapitate should call slice" 테스트
vi test/models/zombie_test.rb

test "decapitate should call slice" do
  @zombie.weapon.expects(:slice)
  @zombie.decapitate
end
이 시험은 expects()를 사용하여 Zombie#decapitate()가 수행되었을 때 Weapon#slice()가 호출되는지를 점검하는 테스트이다.
"geolocate calls the Zoogle graveyard locator and gets returns" 테스트
vi test/models/zombie_test.rb

test "geolocate calls the Zoogle graveyard locator and gets returns" do
  Zoogle.expects(:graveyard_locator).with(@zombie.graveyard).returns({latitude: 3, longitude: 4})
  @zombie.geolocate
end
vi app/models/zoogle.rb

class Zoogle
  def self.graveyard_locator(graveyard)
    sleep(2)
    {latitude: 3, longitude: 4}
  end
end
vi app/models/zombie.rb

def geolocate
  loc = Zoogle.graveyard_locator(self.graveyard)
  "#{loc[:latitude]}, #{loc[:longitude]}"
end
expects().with()는 인자가 제대로 넘어가는지를 점검한다. 여기에 returns() mockup을 사용해서 리턴되는 결과값을 검증한다.