Skip to content

Instantly share code, notes, and snippets.

@eitoball
Created March 15, 2010 12:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eitoball/332805 to your computer and use it in GitHub Desktop.
Save eitoball/332805 to your computer and use it in GitHub Desktop.
この文書は、rr の introducting_rr.txt (バージョン0.10.10時点)を日本語訳した文書です。
**RRの紹介**
ダブル Ruby の省略である RR という名前の新しいテストダブルフレームワークを紹介できてうれしく思います。
テストダブルは、[ダブルの説明]です。テストダブルについての詳細を http://xunitpatterns.com/Test%20Double.html で読むことができます。
RR は次の構成をサポートしています:
* Mock
* Stub
* instance_of
* Probe
**Mock**
<pre>
real_user = User.new
mock(User).find('2') {real_user}
</pre>
上記の例では User.find メソッドを上書きして、real_user を返します。
また、find メソッドが 一度、'2'を引数として受け取るを期待を設定します。
**Stub**
<pre>
user = User.new
my_article = articles(:my_article)
stub(user).can_edit?(my_article) {true}
</pre>
上記の例では can_edit? を上書きしています。
そのメソッドが、article を受け取るとき、true を返します。
**instance_of**
あるクラスのインスタンスのmockやstubを作成することができます。
<pre>
stub.instance_of(User).can_edit?(my_article) {true}
</pre>
上記の例は、Userのインスタンスの can_edit? メソッドのstubを作成しています
**Probe**
probe は、本当のメソッドの実装を呼び出して、返り値を傍受して、場合によっては置き換えた返り値を注入するテストダブルの戦略です。
<pre>
my_article = articles(:my_article)
mock.probe(User).find('2') do |real_user|
stub.probe(real_user).can_edit?(my_article) {true}
real_user
end
</pre>
上記の例では、本当の User.find メソッドの呼び出しをして、ブロックの中で返り値を傍受しています。
それから、real_user の can_edit? メソッドを、stub 化して、true を返すように probe 化しています。
**素晴らしい、どのように便利なの?**
どのようなツールにおいても、mock や stub には制限があります。
例えば、mock だけでは、mock 化されたメソッドが、本当のオブジェクトのインターフェースへ従っているかを検証しません。
probe はこの問題を解決します。
probe を加える事は、保証します:
* User.find へのメソッド呼び出しは正しい
* User.find の返り値を検証して、テストダブルを加えることが可能です
* real_user.can_edit? へのメソッド呼び出しが正しい
**mockは使いません。どうして気にする必要があるのですか?**
状態によるテストは常に表明を作るためにもっとも簡単でもっとも直接的な方法です。
しかしながら、相互作用の表明は、システムがどのように適合するかについてのよい文書として仕えます。
相互作用テストは、テストを設定することを簡単にして、テスト間の結合を取り除くことを手助けすることもできます。
ArticlesController#editのアクションのcan edit article を例にとって、状態と相互作用のテストの取り組みを比べてみましょう。
**状態による例**
<pre>
user = users(:bob)
login(user)
my_article = articles(:my_article)
user.can_edit?(my_article).should == false
lambda do
post :edit, :id => my_article.id, :body => "Hello everybody"
end.should raise_error(SecurityTrangressionError)
</pre>
**相互作用による例**
<pre>
user = users(:bob)
login(user)
my_article = articles(:my_article)
mock.probe(user).can_edit? {false}
lambda do
post :edit, :id => my_article.id, :body => "Hello everybody"
end.should raise_error(SecurityTrangressionError)
</pre>
These two examples are interesting because they verify slight different things.
The interaction example states that when can_edit? with @article is called and returns false, a SecurityTrangressionError
is raised.
The state example gives information that bob cannot edit the article, and from that one can infer that
bob trying to edit the article will raise a SecurityTrangressionError.
State based testing tends to be more coupling than interaction based testing.
Note that coupling is not necessarily bad.
The state based example has both interface, data, and knowledge coupling compared to the interaction based test:
* Interface coupling - If can_edit? does not return false, there is an error.
* Fixture Data coupling - If the fixture data changes, there is an error.
* Knowledge coupling - The ArticleController test needs to know how can_edit? returns false
Interface coupling is actually a good thing, because it verifies the User and ArticleController work
together proberly. This sort of testing is functional or integration testing.
The Data and Knowledge coupling are not desirable characteristics because they cause the
developer to be concerned about another part of the system, which takes development time.
It can also cause unwanted test failures when the global fixture data is changed or when a change
occurs that makes the ArticleController's test setup logic incorrect.
Martin Fowler also notes that interaction testing has coupling to the edit action's implementation,
in that the interaction example states that User#can_edit? must be called for the test to pass.
I've found that sometimes this is desirable and sometimes it is not desirable.
The coupling to the implementation encourages more decomposition but it also makes
causes brittleness because changing the edit action to call another method will cause
the test to fail.
I'm not proposing the right solution in this case, because it is dependent on
your situation. One thing to consider is:
* Do you have functional or integration tests for the failure case?
Taking the desirable and undesirable coupling into account
Here is a view example that renders counts:
**State Based Example**
<pre>
user = users(:bob)
user.articles.count.should == 5
user.articles.comments.count.should == 15
user.gold_stars.count.should == 0
render :template => "users/show"
response.body.should include("Articles Posted: 5")
response.body.should include("Comments Received: 15")
response.body.should include("Gold Stars Received: 0")
</pre>
**Interaction Based Example**
<pre>
user = User.new
mock.probe(user.articles).count {5}
mock.probe(user.articles.comments).count {15}
mock.probe(user.gold_stars).count {80}
render :template => "users/show"
response.body.should include("Articles Posted: 5")
response.body.should include("Comments Received: 15")
response.body.should include("Gold Stars Received: 80")
</pre>
The same tradeoffs are present in this view example as in the ActiclesController example,
but the values of each of the tradeoffs are different.
State testing couplings:
* Interface coupling - If count returns a non-number, there is an error.
* Fixture Data coupling - If the fixture data changes, there is an error.
* Knowledge coupling - There is no noticeable knowledge coupling.
Interaction testing couplings:
* Implementation coupling - If the way the count is determined changes, there is an error.
In these examples, it is fair to expect the counts derived from the fixtures to change quite often.
Decoupling the counts from your fixtures yields more of a benefit because the interaction based example
will probably not need to be changed as often as the state based example.
The interaction based example also provides the benefits of:
* being faster because there is no database access
* providing more focus because non-count user data is not important to this example (the interaction example
ignores the user data while the state based approach includes the user data)
* not requiring you to change the fixture data to provide add "Gold Stars Received" because having
"Gold Stars Received: 0" is almost meaningless (It could easily be calling count on something else
that returns 0)
**State vs. Interaction Based testing?**
The examples I provided favor or are neutral to interaction based testing.
This does not mean all testing should be done with interaction testing.
There are many situations where state based testing is more
straightforward and no more coupled than an interaction based test.
Please pick the right toolset for your situation.
In the future, I will blog about different situations and the trade-offs of
using a state based approach, and/or an interaction based approach.
**Extremely Bad Examples**
Since this is a blog post, the examples are short and relatively benign.
However, There are many examples where state and/or interaction
based testing is overused and abused.
Expanding your toolset can help you and your coworkers fix these issues.
There are already several nice Mock/Stub frameworks in the ruby world. These libraries include:
* Mocha
* Flexmock
* Rspec's Mock Framework
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment