kookaburra 0.14.4 → 0.15.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +49 -9
- data/LICENSE.txt +1 -1
- data/README.markdown +142 -188
- data/Rakefile +12 -16
- data/VERSION +1 -1
- data/kookaburra.gemspec +29 -22
- data/lib/kookaburra/api_driver.rb +8 -48
- data/lib/kookaburra/dependency_accessor.rb +24 -0
- data/lib/kookaburra/exceptions.rb +12 -0
- data/lib/kookaburra/given_driver.rb +55 -8
- data/lib/kookaburra/json_api_driver.rb +76 -0
- data/lib/kookaburra/rack_driver.rb +65 -0
- data/lib/kookaburra/test_data.rb +70 -46
- data/lib/kookaburra/test_helpers.rb +101 -0
- data/lib/kookaburra/ui_driver/ui_component.rb +173 -90
- data/lib/kookaburra/ui_driver.rb +69 -17
- data/lib/kookaburra.rb +70 -151
- data/spec/kookaburra/json_api_driver_spec.rb +34 -0
- data/spec/kookaburra/rack_driver_spec.rb +36 -0
- data/spec/kookaburra/test_data_spec.rb +31 -0
- data/spec/kookaburra/test_helpers_spec.rb +48 -0
- data/spec/kookaburra/ui_driver/ui_component_spec.rb +178 -0
- data/spec/kookaburra/ui_driver_spec.rb +27 -0
- data/spec/kookaburra_integration_spec.rb +312 -0
- data/spec/kookaburra_spec.rb +64 -0
- data/spec/support/shared_examples/it_has_a_dependency_accessor.rb +26 -0
- metadata +49 -30
- data/lib/kookaburra/assertion.rb +0 -32
- data/lib/kookaburra/ui_driver/mixins/has_browser.rb +0 -30
- data/lib/kookaburra/ui_driver/mixins/has_strategies.rb +0 -52
- data/lib/kookaburra/ui_driver/mixins/has_ui_component.rb +0 -45
- data/test/helper.rb +0 -30
- data/test/kookaburra/assertion_test.rb +0 -68
- data/test/kookaburra/test_data_test.rb +0 -28
- data/test/kookaburra/ui_driver/mixins/has_browser_test.rb +0 -38
- data/test/kookaburra/ui_driver/ui_component_test.rb +0 -101
- data/test/kookaburra/ui_driver_test.rb +0 -54
- data/test/kookaburra_test.rb +0 -188
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
CHANGED
@@ -7,11 +7,12 @@ gem 'rack-test'
|
|
7
7
|
# Add dependencies to develop your gem here.
|
8
8
|
# Include everything needed to run rake, tests, features, etc.
|
9
9
|
group :development do
|
10
|
-
gem '
|
10
|
+
gem 'rspec'
|
11
|
+
gem 'capybara'
|
11
12
|
gem 'yard'
|
12
13
|
gem 'redcarpet', '~> 1.0' # used to format documentation
|
13
|
-
gem 'bundler'
|
14
14
|
gem 'jeweler'
|
15
15
|
gem 'rcov'
|
16
16
|
gem 'reek'
|
17
|
+
gem 'sinatra'
|
17
18
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,46 +1,86 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
activesupport (3.2.
|
4
|
+
activesupport (3.2.2)
|
5
5
|
i18n (~> 0.6)
|
6
6
|
multi_json (~> 1.0)
|
7
|
+
capybara (1.1.2)
|
8
|
+
mime-types (>= 1.16)
|
9
|
+
nokogiri (>= 1.3.3)
|
10
|
+
rack (>= 1.0.0)
|
11
|
+
rack-test (>= 0.5.4)
|
12
|
+
selenium-webdriver (~> 2.0)
|
13
|
+
xpath (~> 0.1.4)
|
14
|
+
childprocess (0.3.1)
|
15
|
+
ffi (~> 1.0.6)
|
16
|
+
diff-lcs (1.1.3)
|
17
|
+
ffi (1.0.11)
|
7
18
|
git (1.2.5)
|
8
19
|
i18n (0.6.0)
|
9
|
-
jeweler (1.
|
20
|
+
jeweler (1.8.3)
|
10
21
|
bundler (~> 1.0)
|
11
22
|
git (>= 1.2.5)
|
12
23
|
rake
|
13
|
-
|
14
|
-
|
15
|
-
|
24
|
+
rdoc
|
25
|
+
json (1.6.5)
|
26
|
+
mime-types (1.17.2)
|
27
|
+
multi_json (1.1.0)
|
28
|
+
nokogiri (1.5.0)
|
29
|
+
rack (1.4.1)
|
30
|
+
rack-protection (1.2.0)
|
31
|
+
rack
|
16
32
|
rack-test (0.6.1)
|
17
33
|
rack (>= 1.0)
|
18
34
|
rake (0.9.2.2)
|
19
35
|
rcov (1.0.0)
|
36
|
+
rdoc (3.12)
|
37
|
+
json (~> 1.4)
|
20
38
|
redcarpet (1.17.2)
|
21
39
|
reek (1.2.8)
|
22
40
|
ruby2ruby (~> 1.2)
|
23
41
|
ruby_parser (~> 2.0)
|
24
42
|
sexp_processor (~> 3.0)
|
43
|
+
rspec (2.8.0)
|
44
|
+
rspec-core (~> 2.8.0)
|
45
|
+
rspec-expectations (~> 2.8.0)
|
46
|
+
rspec-mocks (~> 2.8.0)
|
47
|
+
rspec-core (2.8.0)
|
48
|
+
rspec-expectations (2.8.0)
|
49
|
+
diff-lcs (~> 1.1.2)
|
50
|
+
rspec-mocks (2.8.0)
|
25
51
|
ruby2ruby (1.3.1)
|
26
52
|
ruby_parser (~> 2.0)
|
27
53
|
sexp_processor (~> 3.0)
|
28
54
|
ruby_parser (2.3.1)
|
29
55
|
sexp_processor (~> 3.0)
|
30
|
-
|
31
|
-
|
56
|
+
rubyzip (0.9.6.1)
|
57
|
+
selenium-webdriver (2.20.0)
|
58
|
+
childprocess (>= 0.2.5)
|
59
|
+
ffi (~> 1.0)
|
60
|
+
multi_json (~> 1.0)
|
61
|
+
rubyzip
|
62
|
+
sexp_processor (3.1.0)
|
63
|
+
sinatra (1.3.2)
|
64
|
+
rack (~> 1.3, >= 1.3.6)
|
65
|
+
rack-protection (~> 1.2)
|
66
|
+
tilt (~> 1.3, >= 1.3.3)
|
67
|
+
tilt (1.3.3)
|
68
|
+
xpath (0.1.4)
|
69
|
+
nokogiri (~> 1.3)
|
70
|
+
yard (0.7.5)
|
32
71
|
|
33
72
|
PLATFORMS
|
34
73
|
ruby
|
35
74
|
|
36
75
|
DEPENDENCIES
|
37
76
|
activesupport (>= 3.0)
|
38
|
-
|
77
|
+
capybara
|
39
78
|
i18n
|
40
79
|
jeweler
|
41
|
-
minitest
|
42
80
|
rack-test
|
43
81
|
rcov
|
44
82
|
redcarpet (~> 1.0)
|
45
83
|
reek
|
84
|
+
rspec
|
85
|
+
sinatra
|
46
86
|
yard
|
data/LICENSE.txt
CHANGED
data/README.markdown
CHANGED
@@ -3,6 +3,20 @@
|
|
3
3
|
Kookaburra is a framework for implementing the [Window Driver] [Window Driver] pattern in
|
4
4
|
order to keep acceptance tests maintainable.
|
5
5
|
|
6
|
+
## WARNING: Significant Changes since 0.14.x ##
|
7
|
+
|
8
|
+
As of 0.15.0, Kookaburra has been rewritten from the ground up. The original
|
9
|
+
(up through 0.14.x) version was extracted from another project in which the
|
10
|
+
testing library was being used. Unfortunately, this meant that the code in
|
11
|
+
Kookaburra itself did not have very good test coverage, because it was being
|
12
|
+
tested indirectly by the fact of its usage in the other project. What we've
|
13
|
+
found is that a *lot* of complexity was sneaking into Kookaburra due to its
|
14
|
+
having been developed without much focused TDD.
|
15
|
+
|
16
|
+
Starting with 0.15.0, we are treating the previous versions as a spike. They
|
17
|
+
were really useful for learning about the approach, but the code has enough
|
18
|
+
design flaws that its best just to toss it.
|
19
|
+
|
6
20
|
## Installation ##
|
7
21
|
|
8
22
|
Kookaburra is available as a Rubygem and [published on Rubygems.org] [Kookaburra
|
@@ -19,58 +33,58 @@ following:
|
|
19
33
|
|
20
34
|
## Setup ##
|
21
35
|
|
22
|
-
Kookaburra
|
23
|
-
|
36
|
+
Kookaburra abstracts some common patterns for implementing the Window Driver
|
37
|
+
pattern for tests of Ruby web applications built on [Rack] [Rack]. You will need
|
24
38
|
to tell Kookaburra which classes contain the specific Domain Driver
|
25
39
|
implementations for your application as well as which driver to use for running
|
26
|
-
the tests (currently only tested with [Capybara] [Capybara]).
|
27
|
-
Domain Driver layer are discussed below, but in general you will need the
|
28
|
-
following in a locations such as `lib/my_application/kookaburra.rb` (replace
|
29
|
-
`MyApplication` with a module name suitable to your actual application:
|
30
|
-
|
31
|
-
module MyApplication
|
32
|
-
module Kookaburra
|
33
|
-
::Kookaburra.adapter = Capybara
|
34
|
-
|
35
|
-
# Note: the following assigned classes are defined under your
|
36
|
-
# application's namespace, e.g. MyApplication::Kookaburra::APIDriver
|
37
|
-
::Kookaburra.api_driver = APIDriver
|
38
|
-
::Kookaburra.given_driver = GivenDriver
|
39
|
-
::Kookaburra.ui_driver = UIDriver
|
40
|
-
|
41
|
-
::Kookaburra.test_data_setup do
|
42
|
-
provide_collection :accounts
|
43
|
-
# See section on Test Data for more examples of what can go here.
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
40
|
+
the tests (currently only tested with [Capybara] [Capybara]).
|
47
41
|
|
48
42
|
### RSpec ###
|
49
43
|
|
50
44
|
For [RSpec] [RSpec] integration tests, just add the following to
|
51
45
|
`spec/support/kookaburra_setup.rb`:
|
52
46
|
|
53
|
-
require '
|
47
|
+
require 'kookaburra/test_helpers'
|
48
|
+
require 'my_app/kookaburra/api_driver'
|
49
|
+
require 'my_app/kookaburra/given_driver'
|
50
|
+
require 'my_app/kookaburra/ui_driver'
|
51
|
+
|
52
|
+
Kookaburra.configuration = {
|
53
|
+
:api_driver_class => MyApp::Kookaburra::APIDriver,
|
54
|
+
:given_driver_class => MyApp::Kookaburra::GivenDriver,
|
55
|
+
:ui_driver_class => MyApp::Kookaburra::UIDriver,
|
56
|
+
:browser => Capybara,
|
57
|
+
:server_error_detection => { |browser|
|
58
|
+
browser.has_css?('head title', :text => 'Internal Server Error')
|
59
|
+
}
|
60
|
+
}
|
54
61
|
|
55
62
|
RSpec.configure do |c|
|
56
|
-
c.include(Kookaburra, :type => :request)
|
63
|
+
c.include(Kookaburra::TestHelpers, :type => :request)
|
57
64
|
end
|
58
65
|
|
59
66
|
### Cucumber ###
|
60
67
|
|
61
68
|
For [Cucumber] [Cucumber], add the following to `features/support/kookaburra_setup.rb`:
|
62
69
|
|
63
|
-
require '
|
70
|
+
require 'kookaburra/test_helpers'
|
71
|
+
require 'my_app/kookaburra/api_driver'
|
72
|
+
require 'my_app/kookaburra/given_driver'
|
73
|
+
require 'my_app/kookaburra/ui_driver'
|
64
74
|
|
65
|
-
Kookaburra.
|
66
|
-
|
75
|
+
Kookaburra.configuration = {
|
76
|
+
:api_driver_class => MyApp::Kookaburra::APIDriver,
|
77
|
+
:given_driver_class => MyApp::Kookaburra::GivenDriver,
|
78
|
+
:ui_driver_class => MyApp::Kookaburra::UIDriver,
|
79
|
+
:browser => Capybara,
|
80
|
+
:server_error_detection => { |browser|
|
81
|
+
browser.has_css?('head title', :text => 'Internal Server Error')
|
82
|
+
}
|
83
|
+
}
|
67
84
|
|
68
|
-
|
69
|
-
# Ensure that there isn't state-leakage between scenarios
|
70
|
-
kookaburra_reset!
|
71
|
-
end
|
85
|
+
World(Kookaburra::TestHelpers)
|
72
86
|
|
73
|
-
This will cause the #
|
87
|
+
This will cause the #k, #given and #ui methods will be available in your
|
74
88
|
Cucumber step definitions.
|
75
89
|
|
76
90
|
## Defining Your Testing DSL ##
|
@@ -86,7 +100,7 @@ Kookaburra has the following layers:
|
|
86
100
|
etc.)
|
87
101
|
3. The **Domain Driver** (Kookaburra::GivenDriver and Kookaburra::UIDriver)
|
88
102
|
4. The **Window Driver** (Kookaburra::UIDriver::UIComponent)
|
89
|
-
5. The **Application Driver** (Capybara and
|
103
|
+
5. The **Application Driver** (Capybara and Kookaburra::RackDriver)
|
90
104
|
|
91
105
|
### The Business Specification Language ###
|
92
106
|
|
@@ -138,14 +152,10 @@ anywhere outside of your test implementation (such as within `UIDriver` or
|
|
138
152
|
`UIComponent` subclasses.) Doing so will tightly couple your Domain Driver
|
139
153
|
and/or Window Driver implementation to a specific testing library.
|
140
154
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
using it as a base. However, this method should be used only for the purpose
|
146
|
-
of short-circuiting your Domain Driver with an informative error message, not
|
147
|
-
to test the results of your operations as you would at the test implementation
|
148
|
-
layer.
|
155
|
+
`Kookaburra::UIDriver::UIComponent` does provide an `#assert` method for use
|
156
|
+
inside your own UIComponents. This method exists to verify preconditions and
|
157
|
+
provide more informative error messages; it is not intended to be used to make
|
158
|
+
test verifications.
|
149
159
|
|
150
160
|
Given the Cucumber scenario above, here is how the test implementation layer
|
151
161
|
might look:
|
@@ -177,15 +187,15 @@ might look:
|
|
177
187
|
end
|
178
188
|
|
179
189
|
Then "I see my order summary" do
|
180
|
-
ui.should
|
190
|
+
ui.order_summary.should be_visible
|
181
191
|
end
|
182
192
|
|
183
193
|
Then "I see that my default payment options will be used" do
|
184
|
-
ui.should
|
194
|
+
ui.order_summary.payment_options.should == k.get_data(:default_payment_options)[:my_account]
|
185
195
|
end
|
186
196
|
|
187
197
|
Then "I see that my default shipping options will be used" do
|
188
|
-
ui.should
|
198
|
+
ui.order_summary.shipping_options.should == k.get_data(:default_shipping_options)[:my_account]
|
189
199
|
end
|
190
200
|
|
191
201
|
The step definitions contain neither explicitly shared state (instance
|
@@ -199,11 +209,11 @@ scenario, it's not a big deal to have the following in your step definitions (as
|
|
199
209
|
long as the author of the spec confirms that they really mean the same thing):
|
200
210
|
|
201
211
|
Then "I see my order summary" do
|
202
|
-
ui.should
|
212
|
+
ui.order_summary.should be_visible
|
203
213
|
end
|
204
214
|
|
205
215
|
Then "I see a summary of my order" do
|
206
|
-
ui.should
|
216
|
+
ui.order_summary.should be_visible
|
207
217
|
end
|
208
218
|
|
209
219
|
The step definitions are nothing more than a natural language reference to an
|
@@ -234,131 +244,116 @@ Using RSpec, the test implementation would be as follows:
|
|
234
244
|
ui.choose_to_check_out
|
235
245
|
|
236
246
|
ui.order_summary.should be_visible
|
237
|
-
ui.should
|
238
|
-
ui.should
|
247
|
+
ui.order_summary.payment_options.should == k.get_data(:default_payment_options)[:my_account]
|
248
|
+
ui.order_summary.shipping_options.should == k.get_data(:default_shipping_options)[:my_account]
|
239
249
|
end
|
240
250
|
end
|
241
251
|
|
242
252
|
### The Domain Driver ###
|
243
253
|
|
244
254
|
The Domain Driver layer is where you build up an internal DSL that describes the
|
245
|
-
business concepts of your application at a fairly high level. It consists of
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
tests, and the UIDriver (available via `#ui`) for describing the tasks that a
|
250
|
-
user can accomplish with the application.
|
255
|
+
business concepts of your application at a fairly high level. It consists of two
|
256
|
+
top-level drivers: the `GivenDriver` (available via `#given`) used to set up
|
257
|
+
state for your tests and the UIDriver (available via `#ui`) for describing the
|
258
|
+
tasks that a user can accomplish with the application.
|
251
259
|
|
252
260
|
#### Test Data ####
|
253
261
|
|
254
262
|
`Kookaburra::TestData` is the component via which the `GivenDriver` and the
|
255
263
|
`UIDriver` share information. For instance, if you create a user account via the
|
256
264
|
`GivenDriver`, you would store the login credentials for that account in the
|
257
|
-
`TestData` instance, so the UIDriver knows what to use when you tell it to
|
265
|
+
`TestData` instance, so the `UIDriver` knows what to use when you tell it to
|
258
266
|
`#sign_in`. This is what allows the Cucumber step definitions to remain free
|
259
267
|
from explicitly shared state.
|
260
268
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
`Kookaburra.test_data_setup` with a block (usually in your
|
265
|
-
`lib/my_application/kookaburra.rb` file):
|
266
|
-
|
267
|
-
module MyApplication
|
268
|
-
module Kookaburra
|
269
|
-
# ...
|
270
|
-
::Kookaburra.test_data_setup do
|
271
|
-
provide_collection :animals
|
272
|
-
set_default :animal,
|
273
|
-
:name => 'horse'
|
274
|
-
:size => 'large',
|
275
|
-
:number_of_legs => 4
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
269
|
+
Kookaburra automatically configures your `GivenDriver` and your `UIDriver` to share
|
270
|
+
a `TestData` instance, which is available to both of them via their `#test_data`
|
271
|
+
method.
|
279
272
|
|
280
|
-
|
281
|
-
|
282
|
-
|
273
|
+
The `TestData` instance will return a `TestData::Collection` for any method
|
274
|
+
called on the object. The `TestData::Collection` object behaves like a `Hash`
|
275
|
+
for the most part, however it will raise a `Kookaburra::UnknownKeyError` if you
|
276
|
+
try to access a key that has not yet been assigned a value.
|
283
277
|
|
284
|
-
|
285
|
-
def existing_account(nickname)
|
286
|
-
default_account_data = test_data.default(:account)
|
287
|
-
# do something to create account in application
|
288
|
-
# ...
|
289
|
-
# make the details of the new account available to the rest of the test
|
290
|
-
test_data.set_accounts(nickname, account)
|
291
|
-
end
|
292
|
-
end
|
278
|
+
Here's a quick example of TestData behavor:
|
293
279
|
|
294
|
-
|
295
|
-
def sign_in(account_nickname)
|
296
|
-
# pull stored account details from TestData
|
297
|
-
account_info = test_data.fetch_accounts(account_nickname)
|
280
|
+
test_data = TestData.new
|
298
281
|
|
299
|
-
|
300
|
-
|
301
|
-
|
282
|
+
test_data.widgets[:widget_a] = {:name => 'Widget A'}
|
283
|
+
|
284
|
+
test_data.widgets[:widget_a]
|
285
|
+
#=> {:name => 'Widget A'}
|
286
|
+
|
287
|
+
# this will raise a Kookaburra::UnknownKeyError
|
288
|
+
test_data.widgets[:widget_b]
|
302
289
|
|
303
290
|
#### API Driver ####
|
304
291
|
|
305
292
|
The `Kookaburra::APIDriver` is used to interact with an application's external
|
306
293
|
web services API. You tell Kookaburra about your API by creating a subclass of
|
307
|
-
`Kookaburra::APIDriver` for your application
|
294
|
+
`Kookaburra::APIDriver` for your application. Because different applications may
|
295
|
+
implement different types of APIs, Kookaburra will provide more than one base
|
296
|
+
APIDriver class. At the moment, only a JSON API is supported via
|
297
|
+
`Kookaburra::JsonApiDriver`:
|
308
298
|
|
309
|
-
# lib/
|
299
|
+
# lib/my_app/kookaburra/api_driver.rb
|
310
300
|
|
311
|
-
class
|
301
|
+
class MyApp::Kookaburra::APIDriver < Kookaburra::JsonApiDriver
|
312
302
|
def create_account(account_data)
|
313
|
-
|
314
|
-
hash_from_response_json[:account]
|
303
|
+
post '/api/v1/accounts', account_data
|
315
304
|
end
|
316
305
|
end
|
317
306
|
|
307
|
+
Regardless of the type of APIDriver subclass, the contents of your application's
|
308
|
+
APIDriver should consist mainly of mappings between discrete actions and HTTP
|
309
|
+
requests to the specified URL paths. Each driver will implement `#post`, `#get`,
|
310
|
+
`#put`, `#head`, and `#delete` in such a way that any Ruby data structure
|
311
|
+
provided as parameters will be appropriately translated to the API's required
|
312
|
+
data format, and any response body from the API request will be translated into
|
313
|
+
a Ruby data structure and returned.
|
314
|
+
|
318
315
|
#### Given Driver ####
|
319
316
|
|
320
|
-
The `Kookaburra::GivenDriver` is used to create a particular "preexisting"
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
317
|
+
The `Kookaburra::GivenDriver` is used to create a particular "preexisting" state
|
318
|
+
within your application's data and ensure you have a handle to that data (when
|
319
|
+
needed) prior to interacting with the UI. You will create a subclass of
|
320
|
+
`Kookaburra::GivenDriver` in which you will create part of the Domain Driver DSL
|
321
|
+
for your application:
|
325
322
|
|
326
|
-
# lib/
|
323
|
+
# lib/my_app/kookaburra/given_driver.rb
|
327
324
|
|
328
|
-
class
|
325
|
+
class MyApp::Kookaburra::GivenDriver < Kookaburra::GivenDriver
|
329
326
|
def existing_account(nickname)
|
330
|
-
|
331
|
-
# password
|
332
|
-
account_data = test_data.default(:account)
|
327
|
+
account_data = {:display_name => 'John Doe', :password => 'a password'}
|
333
328
|
account_data[:username] = "test-user-#{`uuidgen`.strip}"
|
334
|
-
account_data[:password] = account_data[:username] + "-password"
|
335
329
|
|
336
330
|
# use the API to create the account in the application
|
337
|
-
|
331
|
+
result = api.create_account(account_data)
|
338
332
|
|
339
333
|
# merge in the password (since API doesn't return it) and store details
|
340
334
|
# in the TestData instance
|
341
|
-
|
342
|
-
test_data.
|
335
|
+
result.merge!(:password => account_data[:password])
|
336
|
+
test_data.accounts[nickname] = account_details
|
343
337
|
end
|
344
338
|
end
|
345
339
|
|
346
340
|
Although there is nothing that actually *prevents* you from either interacting
|
347
341
|
with the UI or directly manipulating your application via calls into the model
|
348
|
-
from the `GivenDriver`, both
|
342
|
+
from the `GivenDriver`, both should be avoided. In the first case, the
|
349
343
|
`GivenDriver`'s purpose is to describe state that exists *before* the user
|
350
344
|
interaction that is being tested. Although this state may be the result of a
|
351
|
-
previous user interaction, your tests will
|
352
|
-
|
345
|
+
previous user interaction, your tests will be much, much faster if you create
|
346
|
+
this state via API calls rather than driving a web browser.
|
353
347
|
|
354
|
-
In the second case, by avoiding
|
348
|
+
In the second case, by avoiding the manipulation of your applications's state at the
|
355
349
|
code level and instead doing so via an external API, it is much less likely that
|
356
|
-
you will
|
350
|
+
you will create a state that your application can't actually get into in a
|
357
351
|
production environment. Additionally, this opens up the possibility of running
|
358
352
|
your tests against a "remote" server where you would not have access to the
|
359
353
|
application internals. ("Remote" in the sense that it is not in the same Ruby
|
360
354
|
process as your running tests, although it may or may not be on the same
|
361
|
-
machine.
|
355
|
+
machine. Note that this is not currently possible with Kookaburra due to our
|
356
|
+
current reliance on Rack::Test.)
|
362
357
|
|
363
358
|
#### UI Driver ####
|
364
359
|
|
@@ -367,26 +362,20 @@ application's user interface using the Window Driver pattern. You will subclass
|
|
367
362
|
`Kookaburra::UIDriver` for your application and implement your testing DSL
|
368
363
|
within your subclass:
|
369
364
|
|
370
|
-
# lib/
|
365
|
+
# lib/my_app/kookaburra/ui_driver.rb
|
371
366
|
|
372
|
-
class
|
373
|
-
# makes an instance of
|
374
|
-
# available via the
|
375
|
-
ui_component :sign_in_screen
|
367
|
+
class MyApp::Kookaburra::UIDriver < Kookaburra::UIDriver
|
368
|
+
# makes an instance of MyApp::Kookaburra::UIDriver::SignInScreen
|
369
|
+
# available via the instance method #sign_in_screen
|
370
|
+
ui_component :sign_in_screen, SignInScreen
|
376
371
|
|
377
372
|
def sign_in(account_nickname)
|
378
|
-
account = test_data.
|
379
|
-
|
373
|
+
account = test_data.accounts[account_nickname]
|
374
|
+
sign_in_screen.show
|
380
375
|
sign_in_screen.submit_login(account[:username], account[:password])
|
381
376
|
end
|
382
377
|
end
|
383
378
|
|
384
|
-
The call to `Kookaburra::UIDriver.ui_component` defines the UIComponent accessor
|
385
|
-
as a private method in order to discourage accessing your UIComponent objects
|
386
|
-
directly in the test implementation layer. Instead, you should build out your
|
387
|
-
testing DSL in the `UIDriver` subclass as was done with the `#sign_in` method
|
388
|
-
above.
|
389
|
-
|
390
379
|
### The Window Driver Layer ###
|
391
380
|
|
392
381
|
While your `GivenDriver` and `UIDriver` provide a DSL that represents actions
|
@@ -395,81 +384,48 @@ layer describes the individual user interface components that the user interacts
|
|
395
384
|
with to perform these tasks. By describing each interface component using an OOP
|
396
385
|
approach, it is much easier to maintain your acceptance/integration tests,
|
397
386
|
because the implementation details of each component are captured in a single
|
398
|
-
place.
|
399
|
-
single test that needs to log a user into the
|
400
|
-
SignInScreen class.
|
387
|
+
place. For example, if/when the implementation of your application's sign in
|
388
|
+
screen changes, you can fix every single test that needs to log a user into the
|
389
|
+
system just by updating the `SignInScreen` class.
|
401
390
|
|
402
391
|
You describe the various user interface components by sub-classing
|
403
392
|
`Kookaburra::UIDriver::UIComponent`:
|
404
393
|
|
405
|
-
# lib/
|
394
|
+
# lib/my_app/ui_driver/sign_in_screen.rb
|
406
395
|
|
407
|
-
class
|
408
|
-
component_locator
|
409
|
-
|
396
|
+
class MyApp::Kookaburra::UIDriver::SignInScreen < Kookaburra::UIDriver::UIComponent
|
397
|
+
def component_locator
|
398
|
+
'#new_user_session'
|
399
|
+
end
|
400
|
+
|
401
|
+
def component_path
|
402
|
+
'/session/new'
|
403
|
+
end
|
410
404
|
|
411
405
|
def username
|
412
|
-
|
406
|
+
find('#session_username').value
|
413
407
|
end
|
414
408
|
|
415
409
|
def username=(new_value)
|
416
|
-
fill_in
|
410
|
+
fill_in '#session_username', :with => new_value
|
417
411
|
end
|
418
412
|
|
419
413
|
def password
|
420
|
-
|
414
|
+
find('#session_password').value
|
421
415
|
end
|
422
416
|
|
423
417
|
def password=(new_value)
|
424
|
-
fill_in
|
418
|
+
fill_in '#session_password', :with => new_value
|
425
419
|
end
|
426
420
|
|
427
|
-
def submit
|
421
|
+
def submit
|
428
422
|
click_on('Sign In')
|
429
|
-
no_500_error!
|
430
423
|
end
|
431
424
|
|
432
425
|
def submit_login(username, password)
|
433
426
|
self.username = username
|
434
427
|
self.password = password
|
435
|
-
submit
|
436
|
-
end
|
437
|
-
end
|
438
|
-
|
439
|
-
A `UIComponent` subclass can also contain nested components of its own. For
|
440
|
-
instance:
|
441
|
-
|
442
|
-
class MyApplication::Kookaburra::UIDriver::UserList::NewUserForm < Kookaburra::UIDriver::UIComponent
|
443
|
-
component_locator '#new_user_form'
|
444
|
-
end
|
445
|
-
|
446
|
-
class MyApplication::Kookaburra::UIDriver::UserList < Kookaburra::UIDriver::UIComponent
|
447
|
-
component_locator '#user_list'
|
448
|
-
|
449
|
-
ui_component :new_user_form
|
450
|
-
end
|
451
|
-
|
452
|
-
In this case, `UserList#new_user_form` is still defined as a private method. In
|
453
|
-
order to manipulate it from your domain driver, you can define either explicit
|
454
|
-
methods or delegators on `UserList`:
|
455
|
-
|
456
|
-
class MyApplication::Kookaburra::UIDriver::UserList < Kookaburra::UIDriver::UIComponent
|
457
|
-
def fill_in_new_user_form(username, password, full_name)
|
458
|
-
new_user_form.username = username
|
459
|
-
new_user_form.password = password
|
460
|
-
new_user_form.full_name = full_name
|
461
|
-
end
|
462
|
-
|
463
|
-
delegate :submit!, :to => :new_user_form, :prefix => true
|
464
|
-
end
|
465
|
-
|
466
|
-
class MyApplication::Kookaburra::UIDriver < Kookaburra::UIDriver
|
467
|
-
ui_component :user_list
|
468
|
-
|
469
|
-
def create_new_user_with_valid_data
|
470
|
-
user = default_user_data # factory method defined elsewhere
|
471
|
-
user_list.fill_in_new_user_form(user[:username], user[:password], user[:full_name])
|
472
|
-
user_list.new_user_form_submit!
|
428
|
+
submit
|
473
429
|
end
|
474
430
|
end
|
475
431
|
|
@@ -478,11 +434,9 @@ methods or delegators on `UserList`:
|
|
478
434
|
`Kookaburra::APIDriver`, `Kookaburra::UIDriver` and
|
479
435
|
`Kookaburra::UIDriver::UIComponent` rely on the Application Driver layer to
|
480
436
|
interact with your application. In the case of the `APIDriver`, Kookaburra uses
|
481
|
-
`
|
482
|
-
`UIComponent` rely on whatever is
|
483
|
-
we have only used Capybara as the application driver for Kookaburra
|
484
|
-
|
485
|
-
Kookaburra.adapter = Capybara
|
437
|
+
`Kookaburra::RackDriver` to send HTTP requests to your application. The `UIDriver` and
|
438
|
+
`UIComponent` rely on whatever is passed to `Kookaburra.new` as the `:browser`
|
439
|
+
option. Presently, we have only used Capybara as the application driver for Kookaburra.
|
486
440
|
|
487
441
|
It's possible that something other than Capybara could be passed in, as long as
|
488
442
|
that something presented the same API. In reality, using something other than
|
@@ -508,7 +462,7 @@ and send us a [GitHub pull request] [Pull Request] with your changes.
|
|
508
462
|
|
509
463
|
## Copyright ##
|
510
464
|
|
511
|
-
Copyright © 2011
|
465
|
+
Copyright © 2011 John Wilger. See LICENSE.txt for
|
512
466
|
further details.
|
513
467
|
|
514
468
|
[Window Driver]: http://martinfowler.com/eaaDev/WindowDriver.html "Window Driver - Martin Fowler"
|