loading presentation...
Toggleable Mocks
切り替え可能なモック
Testing in a Service Oriented Architecture
サービス指向アーキテクチャにおけるテスト
Andy Delcambre
アンディー・デルカム
(rhymes with "Welcome")
「ウェルカム」と同じ脚韻
furnari
If you understand english raise your hand
Services
サービス
aka SOA
別名 SOA
Who knows SOA?
SOAをご存知ですか?
ducdigital
Who uses SOA?
SOAをお使いですか?
colorblindpicaso
headlouse
Single Responsibility Principle
単一責任の原則
aka The Unix Philosophy
別名 UNIXの精神
do one thing and do it well
たった一つの事を上手にやる
Smaller Code Bases
少ないコード
Each piece can be deployed separately
それぞれの部品を別々にデプロイ出来る
Different teams can work on different services
複数のチームが異るサービスを開発できる
Engine Yard Architecture
弊社 Engine Yard の例
But how do we test?
でもどうやってテストする?
It's hard
難しいです
coda2
The Example
例
User Store
ユーザ
The server
サーバのコード
class Users < Sinatra::Base
post "/users/" do
User.create(params[:user]).to_json
end
get "/users/:id" do
User.get(id).to_json
end
end
The client
クライアントのコード
class User
URL = "http://users.example.org/users"
def self.get(id)
res = RestClient.get("#{URL}/#{id}")
new JSON.parse(res)
end
def self.create(attrs)
res = RestClient.post("#{URL}",
{:user => attrs}.to_json)
new JSON.parse(res.body)
end
end
describe "with a user" do
it "finds it" do
u = User.create(:name => "Foo")
u = User.get(u.id)
u.name.should == "Foo"
end
end
A good starting place
初めとしてはまあまあ
Spin up a development server and start testing
開発サーバを起動してテストを始めましょう
Very good coverage
高度なコードカバレッジ
Can't reset state
テストが始まったらオブジェクトの状態を元に戻せない
You have to teardown everything after every test
テストの度に全てやり直す必要がある
Maybe a mock is what we need?
もしかして、「モック」が必要かも
Mock
モック
- Objects that act like the real thing
- 本物の様に振る舞うオブジェクト
- But with expectations on the calls made
- 但しどのメソッドが呼び出されるか知っている物
it "works" do
user.should_receive(:send_email).once
user.process_upgrade
end
Stub
スタブ
Replacing certain methods on an object with dummy data
オブジェクトに定義されているメソッドを仮のデータで置き換える物
it "works" do
user.stub!(:name).and_return("Andy")
user.greeting.should == "Hello Andy"
end
Fake
フェイク
A replacement object that behaves like the real thing, but isn't suitable for production
本物の様に振る舞う代替のオブジェクトだが、本番には使えない物
Example: an in memory sqlite database
例: メモリに格納されているsqliteデータベース
People tend to refer to all of these as "mocks"
これら全てを「モック」と呼ぶ人も多いです
Hence the title of my talk
それで演題にも「モック」という言葉を使いました
Stubbing things out
スタブを使う
it "works" do
user = mock(User)
User.stub!(:get).with(1).
and_return(user)
user.stub!(:name).and_return("foo")
User.get(1).name.should == "foo"
end
A logical next step
次の段階としては妥当なところ
Because nothing is tested
テストしてないから
We can do better, even with stubs
スタブを使っても改善は可能
Fakeweb
https://github.com/chrisk/fakeweb/
Stubs for net/http
net/http に相当するスタブ
NOTE: Not a fake
フェイクでない事に注意
Despite the name
名前に騙されないように
describe "with a user" do
it "finds it" do
FakeWeb.register_uri(:get,
"http://users.example.org/users/1",
:body => {:name => "Foo"}.to_json)
u = User.get(1)
u.name.should == "Foo"
end
end
it "creates a user" do
FakeWeb.register_uri(:post,
"http://users.example.org/users/",
:body => {:name => "Foo"}.to_json)
u = User.create(:name => "Foo")
u.name.should == "Foo"
end
Not bad
まずますの出来
mar00ned
Testing at HTTP layer
HTTPレイヤーでのテスト
Good coverage of the library
ライブラリを恙(つつが)なく網羅
Problems
問題点
quinnanya
Reaches around the library to setup state
ライブラリを無視して初期化
Tests do not use the public API for setup
パブリックAPIを使わずに初期化
Tests must have intimate knowledge of implementation
テストを書く際にFakewebの内部実装を熟知する必要がある
Build a fake into the library
フェイクをライブラリに組み込む
A fake ruby object
Rubyオブジェクトであるフェイク
The Fake
class User
def self.testing!
@users = {}
def self.get(id)
new(@users[id])
end
def self.create(atrs)
atrs = atrs.merge(:id => next_id)
new(@users[atrs[:id]] = atrs)
end
end
end
The Test
describe "with a user" do
it "finds it" do
u = User.create(:name => "Foo")
u = User.get(u.id)
u.name.should == "Foo"
end
end
Getting Better!
段々良くなってますね!
vegaseddie
Tests aren't coupled to the implementation
テストと実装が隔離されています
The fake is toggleable!
フェイクは切り替え可能
Still not great
でもまだ今一つです
atonal
Not at HTTP layer
テストはHTTPレイヤーではない
Therefore, a lot of code is not tested
だから、テストされていない部分も多い
Artifice
http://github.com/wycats/artifice
Artifice stubs net/http with a Rack App
Artifice では本物の Rack アプリケーションを net/http のスタブとして使います
An entire fake at the http layer
http レイヤーは全てフェイク
class User
def self.testing!
Artifice.activate_with(
FakeUsers.new)
end
end
class FakeUsers < Sinatra::Base
@users = {}
post "/users/" do
id = next_id
@users[id] = params[:user].
merge(:id => id)
@users[id].to_json
end
get "/users/:id" do
@users[id].to_json
end
end
Same test
先程と同じテストが使えます
describe "with a user" do
it "finds it" do
u = User.create(:name => "Foo")
u = User.get(u.id)
u.name.should == "Foo"
end
end
robboudon
Best of both worlds
イイトコ取り
Fake at the Rack layer for best coverage
Rackレイヤーでフェイクを使い幅広くテスト
Tests have no knowledge of implementation
テストが実装についての知識を必要としない
So now that we have this...
そろそろ本題に入りますか
# in spec_helper.rb or equivalent
if ENV["MOCK_USERS"] == "true"
User.testing!
end
So what does this gain us?
どのような利点があるのでしょうか
Very fast mocked specs
モックを使ったspecが速い
$ rake spec:mocked
(in /Users/adelcambre/p/ey_sso)
...............................*.....**......
.............................................
.................
Finished in 11.161133 seconds
107 examples, 0 failures, 3 pending
And very thorough integration tests
統合テストが徹底
with a live backend
バックエンドが本物
$ rake spec:unmocked
(in /Users/adelcambre/p/ey_sso)
...............................*.....**......
.............................................
.................
Finished in 411.887547 seconds
107 examples, 0 failures, 3 pending
The tests do not have to change
テストを弄る必要が無い
So Continuous Integration runs both
だから継続テストで両方走る
$ rake spec:ci
(in /Users/adelcambre/p/ey_sso)
...............................*.....**......
.............................................
.................
Finished in 429.935862 seconds
107 examples, 0 failures, 6 pending
(in /Users/adelcambre/p/ey_sso)
...............................*.....**......
.............................................
.................
Finished in 11.421828 seconds
107 examples, 0 failures, 3 pending
rake spec:ci 7:32.42 total
We don't deploy until both suites are green
弊社では両方のテストスイートがパスするまではデプロイしません
CI Validates the fake
CI(継続的インテグレーション)でフェイクを認証
This gives you composability
これで組立が可能になります
So when writing an app that uses the client
このクライアントを使ったアプリケーションを書く時には
You can toggle the fake on
フェイクを使い
But with a good test suite in the client
クライアントに良いテストスイートを付ける事で
You have verified the fake correct
フェイクの正当性を証明しました
So you rarely need to run without the fake
フェイクなしで走らせる事はあまりありません
Downsides to this pattern?
このパターンの問題点
- assume unmocked in your tests
- 現存のテストはモックを使っていない事が前提
Downsides to this pattern?
このパターンの問題点
- Have to always clean up state
- テストの度に状態を初期化する必要がある
Downsides to this pattern?
このパターンの問題点
- Unique data conflicts
- ユニークなデータの衝突
Artifice just takes a Rack App
Artifice では Rack アプリケーションを使います
The server code is a Rack App
サーバは Rack アプリケーションです
Why not use the actual server as the mock?
実際のサーバをモックとして使えないでしょうか
We have done this in a few limited cases
限られたケースでは実践済み
Artifice.activate_with(
Rack::Builder.new do
map "#{URL_FOR_EY_SSO}/" do
run HaxMockMoiApi.new
end
map "#{URL_FOR_TRESFIESTAS}/" do
run Tresfiestas::Application
end
map "#{URL_FOR_AWSM}/" do
run FakeAWSM
end
map "#{URL_FOR_LISONJA}/" do
run Lisonja
end
map "#{URL_FOR_SECRET_ADMIN}/" do
run SecretAdmin
end
end
Not enough experience to recommend it yet
まだ経験不足で推奨するほどではない
Thanks to Hiro Asari
日本語字幕は浅里さん
for Japanese translations
Thanks to Scott Chacon
スコット・シャコーン氏
for showoff
showoff の開発者
https://github.com/schacon/showoff
Questions?
質問などあれば
http://twitter.com/adelcambre
http://github.com/adelcambre
http://flickr.com/adelcambre