Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 207 lines (160 sloc) 8.183 kb
0b9333c Article changes. Formatted for Typo.
btakita authored
1 **Introducting RR**
4a9af74 Introducing rr text
btakita authored
2
3 I'm pleased to introduce a new Test Double framework names RR, which is short for Double Ruby.
4 A Test Double is [double description]. You can read more about test doubles at http://xunitpatterns.com/Test%20Double.html.
5
6 RR supports the following constructs:
7 * Mock
8 * Stub
9 * instance_of
1edd9d2 Article changes.
btakita authored
10 * Probe
4a9af74 Introducing rr text
btakita authored
11
0b9333c Article changes. Formatted for Typo.
btakita authored
12 **Mock**
13 <pre>
4a9af74 Introducing rr text
btakita authored
14 real_user = User.new
15 mock(User).find('2') {real_user}
0b9333c Article changes. Formatted for Typo.
btakita authored
16 </pre>
4a9af74 Introducing rr text
btakita authored
17
18 The previous example overrides the User.find method and returns real_user. It also sets an expectation
19 that the find method will receive the argument '2' once.
20
0b9333c Article changes. Formatted for Typo.
btakita authored
21 **Stub**
22 <pre>
4a9af74 Introducing rr text
btakita authored
23 user = User.new
24 my_article = articles(:my_article)
25 stub(user).can_edit?(my_article) {true}
0b9333c Article changes. Formatted for Typo.
btakita authored
26 </pre>
4a9af74 Introducing rr text
btakita authored
27
28 The previous example overrides can_edit?. When the method receives the article, it returns true.
29
1edd9d2 Article changes.
btakita authored
30 **instance_of**
31 You can mock or stub instances of a class.
32 <pre>
33 stub.instance_of(User).can_edit?(my_article) {true}
34 </pre>
35
36 The previous example stubs the can_edit? method of any intstance of User.
37
0b9333c Article changes. Formatted for Typo.
btakita authored
38 **Probe**
39
4a9af74 Introducing rr text
btakita authored
40 A probe is a test double strategy that lets the real method implementation be called, and allows you
41 to intercept the return value, and possibly inject you own replacement return value.
0b9333c Article changes. Formatted for Typo.
btakita authored
42 <pre>
4a9af74 Introducing rr text
btakita authored
43 my_article = articles(:my_article)
44 mock.probe(User).find('2') do |real_user|
45 stub.probe(real_user).can_edit?(my_article) {true}
46 real_user
47 end
0b9333c Article changes. Formatted for Typo.
btakita authored
48 </pre>
4a9af74 Introducing rr text
btakita authored
49
50 The previous example, lets the real User.find method call happen, and intercepts its return value inside
51 of the block.
52
53 The real_user's can_edit? method is then stubbed and probed to return true.
54
0b9333c Article changes. Formatted for Typo.
btakita authored
55 **Thats nice, how is it useful?**
56
4a9af74 Introducing rr text
btakita authored
57 As with any tool, Mocks and Stubs have limitations.
58 For example, mocks alone do not verify that the mocked out method conforms to the real object's interface.
59 Probes solve this issue.
60
61 Adding a probe ensures that:
62 * The method call to User.find is valid
63 * The return value of User.find is available to validate and/or add test doubles to
64 * The method call to real_user.can_edit? is valid
65
0b9333c Article changes. Formatted for Typo.
btakita authored
66 **I don't use Mocks. Why should I care?**
67
4a9af74 Introducing rr text
btakita authored
68 State based testing is often the simplest and most straightforward way to make assertions.
69 However Interaction assertions can serve as good documentation of how your system fits together.
70 Interaction tests can also aid you in making your tests easier to set up and removing coupling between tests.
71
72 Lets compare the state and interaction testing approaches in the can edit article example for the
73 ArticlesController#edit action:
74
0b9333c Article changes. Formatted for Typo.
btakita authored
75 **State Based Example**
76 <pre>
4a9af74 Introducing rr text
btakita authored
77 user = users(:bob)
78 login(user)
79 my_article = articles(:my_article)
80 user.can_edit?(my_article).should == false
81
82 proc do
83 post :edit, :id => my_article.id, :body => "Hello everybody"
84 end.should raise_error(SecurityTrangressionError)
0b9333c Article changes. Formatted for Typo.
btakita authored
85 </pre>
4a9af74 Introducing rr text
btakita authored
86
0b9333c Article changes. Formatted for Typo.
btakita authored
87 **Interaction Based Example**
88 <pre>
4a9af74 Introducing rr text
btakita authored
89 user = users(:bob)
90 login(user)
91 my_article = articles(:my_article)
92 mock.probe(user).can_edit? {false}
93
94 proc do
95 post :edit, :id => my_article.id, :body => "Hello everybody"
96 end.should raise_error(SecurityTrangressionError)
0b9333c Article changes. Formatted for Typo.
btakita authored
97 </pre>
4a9af74 Introducing rr text
btakita authored
98
99 These two examples are interesting because they verify slight different things.
d0a5a25 Article changes.
btakita authored
100 The interaction example states that when can_edit? with @article is called and returns false, a SecurityTrangressionError
4a9af74 Introducing rr text
btakita authored
101 is raised.
102 The state example gives information that bob cannot edit the article, and from that one can infer that
103 bob trying to edit the article will raise a SecurityTrangressionError.
104
d0a5a25 Article changes.
btakita authored
105 State based testing tends to be more coupling than interaction based testing.
106 Note that coupling is not necessarily bad.
107 The state based example has both interface, data, and knowledge coupling compared to the interaction based test:
108 * Interface coupling - If can_edit? does not return false, there is an error.
109 * Fixture Data coupling - If the fixture data changes, there is an error.
110 * Knowledge coupling - The ArticleController test needs to know how can_edit? returns false
4a9af74 Introducing rr text
btakita authored
111
d0a5a25 Article changes.
btakita authored
112 Interface coupling is actually a good thing, because it verifies the User and ArticleController work
113 together proberly. This sort of testing is functional or integration testing.
4a9af74 Introducing rr text
btakita authored
114
d0a5a25 Article changes.
btakita authored
115 The Data and Knowledge coupling are not desirable characteristics because they cause the
116 developer to be concerned about another part of the system, which takes development time.
117 It can also cause unwanted test failures when the global fixture data is changed or when a change
118 occurs that makes the ArticleController's test setup logic incorrect.
119
120 Martin Fowler also notes that interaction testing has coupling to the edit action's implementation,
121 in that the interaction example states that User#can_edit? must be called for the test to pass.
122 I've found that sometimes this is desirable and sometimes it is not desirable.
123
124 The coupling to the implementation encourages more decomposition but it also makes
125 causes brittleness because changing the edit action to call another method will cause
126 the test to fail.
127
128 I'm not proposing the right solution in this case, because it is dependent on
129 your situation. One thing to consider is:
130 * Do you have functional or integration tests for the failure case?
131
132 Taking the desirable and undesirable coupling into account
4a9af74 Introducing rr text
btakita authored
133
134 Here is a view example that renders counts:
0b9333c Article changes. Formatted for Typo.
btakita authored
135
136 **State Based Example**
137 <pre>
4a9af74 Introducing rr text
btakita authored
138 user = users(:bob)
139 user.articles.count.should == 5
140 user.articles.comments.count.should == 15
141 user.gold_stars.count.should == 0
142
143 render :template => "users/show"
144 response.body.should include("Articles Posted: 5")
145 response.body.should include("Comments Received: 15")
146 response.body.should include("Gold Stars Received: 0")
0b9333c Article changes. Formatted for Typo.
btakita authored
147 </pre>
4a9af74 Introducing rr text
btakita authored
148
0b9333c Article changes. Formatted for Typo.
btakita authored
149 **Interaction Based Example**
150 <pre>
4a9af74 Introducing rr text
btakita authored
151 user = User.new
152 mock.probe(user.articles).count {5}
153 mock.probe(user.articles.comments).count {15}
154 mock.probe(user.gold_stars).count {80}
155
156 render :template => "users/show"
157 response.body.should include("Articles Posted: 5")
158 response.body.should include("Comments Received: 15")
159 response.body.should include("Gold Stars Received: 80")
0b9333c Article changes. Formatted for Typo.
btakita authored
160 </pre>
4a9af74 Introducing rr text
btakita authored
161
d0a5a25 Article changes.
btakita authored
162 The same tradeoffs are present in this view example as in the ActiclesController example,
163 but the values of each of the tradeoffs are different.
164
165 State testing couplings:
166 * Interface coupling - If count returns a non-number, there is an error.
167 * Fixture Data coupling - If the fixture data changes, there is an error.
168 * Knowledge coupling - There is no noticeable knowledge coupling.
169
170 Interaction testing couplings:
171 * Implementation coupling - If the way the count is determined changes, there is an error.
172
4a9af74 Introducing rr text
btakita authored
173 In these examples, it is fair to expect the counts derived from the fixtures to change quite often.
174 Decoupling the counts from your fixtures yields more of a benefit because the interaction based example
d0a5a25 Article changes.
btakita authored
175 will probably not need to be changed as often as the state based example.
4a9af74 Introducing rr text
btakita authored
176
d0a5a25 Article changes.
btakita authored
177 The interaction based example also provides the benefits of:
4a9af74 Introducing rr text
btakita authored
178 * being faster because there is no database access
179 * providing more focus because non-count user data is not important to this example (the interaction example
180 ignores the user data while the state based approach includes the user data)
181 * not requiring you to change the fixture data to provide add "Gold Stars Received" because having
182 "Gold Stars Received: 0" is almost meaningless (It could easily be calling count on something else
183 that returns 0)
184
0b9333c Article changes. Formatted for Typo.
btakita authored
185 **State vs. Interaction Based testing?**
186
d0a5a25 Article changes.
btakita authored
187 The examples I provided favor or are neutral to interaction based testing.
188 This does not mean all testing should be done with interaction testing.
189 There are many situations where state based testing is more
4a9af74 Introducing rr text
btakita authored
190 straightforward and no more coupled than an interaction based test.
191
d0a5a25 Article changes.
btakita authored
192 Please pick the right toolset for your situation.
193 In the future, I will blog about different situations and the trade-offs of
194 using a state based approach, and/or an interaction based approach.
4a9af74 Introducing rr text
btakita authored
195
0b9333c Article changes. Formatted for Typo.
btakita authored
196 **Extremely Bad Examples**
197
4a9af74 Introducing rr text
btakita authored
198 Since this is a blog post, the examples are short and relatively benign.
d0a5a25 Article changes.
btakita authored
199 However, There are many examples where state and/or interaction
200 based testing is overused and abused.
4a9af74 Introducing rr text
btakita authored
201 Expanding your toolset can help you and your coworkers fix these issues.
202
203 There are already several nice Mock/Stub frameworks in the ruby world. These libraries include:
204 * Mocha
205 * Flexmock
206 * Rspec's Mock Framework
Something went wrong with that request. Please try again.