itg_test 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/README.md +69 -0
- data/itg_test.gemspec +21 -0
- data/lib/itg_test/http_client.rb +82 -0
- data/lib/itg_test/railtie.rb +11 -0
- data/lib/itg_test/test_case.rb +68 -0
- data/lib/itg_test.rb +11 -0
- data/lib/tasks/test_itg.rake +17 -0
- metadata +69 -0
data/.gitignore
ADDED
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# itg_test - simple integration test framework
|
2
|
+
## Description
|
3
|
+
For Rails apps that are too complicated to be tested with the
|
4
|
+
builtin integration test, such as apps that spawn threads
|
5
|
+
that update the same database table.
|
6
|
+
itg_test overcomes this obstacle by simply starting a server
|
7
|
+
and a client in separate processes.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
`gem install itg_test`
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
* Create a `itg` folder under Rail's default test folder by running
|
14
|
+
`mkdir test/itg`.
|
15
|
+
Now your `test` directory structure should look something like
|
16
|
+
|
17
|
+
`$ls test`
|
18
|
+
|
19
|
+
`fixtures functional integration itg test_helper.rb unit`
|
20
|
+
|
21
|
+
* Write integration tests as you would with Rail's default
|
22
|
+
integration test framework. Remember to place them under
|
23
|
+
the `itg` folder instead of Rail's default `integration` folder.
|
24
|
+
For example, create `test/itg/your_app_test.rb` with the following contents
|
25
|
+
```
|
26
|
+
require 'itg_test'
|
27
|
+
|
28
|
+
class YourAppTest < ItgTest::TestCase
|
29
|
+
test 'a complete test' do
|
30
|
+
get '/peanuts'
|
31
|
+
assert_select 'h3 p:nth-child(3)', 'very yummy'
|
32
|
+
...
|
33
|
+
end
|
34
|
+
end
|
35
|
+
```
|
36
|
+
* Run your itg_test tests with `rake test:itg`
|
37
|
+
* itg_test supports the common Rails integration test helpers,
|
38
|
+
including `assert_equal`, `assert_difference` and `assert_select` etc.
|
39
|
+
* You might find the following script that runs all
|
40
|
+
Rails and itg_test tests in one shot handy
|
41
|
+
```
|
42
|
+
desc "all tests"
|
43
|
+
task 'test:all' => :environment do
|
44
|
+
Rake::Task['test'].invoke
|
45
|
+
Rake::Task['test:itg'].invoke
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
## CONTRIBUTORS:
|
50
|
+
|
51
|
+
* awaw fumin (@awawfumin)
|
52
|
+
|
53
|
+
## LICENSE:
|
54
|
+
|
55
|
+
Apache License 2.0
|
56
|
+
|
57
|
+
Copyright (c) 2011-2012, Cardinal Blue
|
58
|
+
|
59
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
60
|
+
you may not use this file except in compliance with the License.
|
61
|
+
You may obtain a copy of the License at
|
62
|
+
|
63
|
+
<http://www.apache.org/licenses/LICENSE-2.0>
|
64
|
+
|
65
|
+
Unless required by applicable law or agreed to in writing, software
|
66
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
67
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
68
|
+
See the License for the specific language governing permissions and
|
69
|
+
limitations under the License.
|
data/itg_test.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'itg_test'
|
3
|
+
s.version = '0.0.0'
|
4
|
+
s.date = '2012-08-29'
|
5
|
+
s.summary = "rails integration test"
|
6
|
+
s.description = "for rails apps that are too complicated to be tested with builtin integration test"
|
7
|
+
s.authors = ["awawfumin"]
|
8
|
+
s.email = 'fumin@cardinalblue.com'
|
9
|
+
s.files = [
|
10
|
+
".gitignore",
|
11
|
+
"lib/itg_test.rb",
|
12
|
+
"lib/itg_test/http_client.rb",
|
13
|
+
"lib/itg_test/railtie.rb",
|
14
|
+
"lib/itg_test/test_case.rb",
|
15
|
+
"lib/tasks/test_itg.rake",
|
16
|
+
"itg_test.gemspec",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.homepage = 'http://cardinalblue.com'
|
20
|
+
s.add_runtime_dependency "nokogiri", [">= 0"]
|
21
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
# Synopsis
|
4
|
+
# c = ItgTest::HTTPClient.new 'http://localhost:3000'
|
5
|
+
# c.get '/scheduled_posts/post_due' => <head></head><body>...</body>
|
6
|
+
|
7
|
+
module ItgTest; end
|
8
|
+
class ItgTest::HTTPClient
|
9
|
+
attr_accessor :cookie
|
10
|
+
def initialize url
|
11
|
+
new_url = if %r|^http://.+$| =~ url || %r|^https://.+$| =~ url
|
12
|
+
url
|
13
|
+
else
|
14
|
+
"http://" + url
|
15
|
+
end
|
16
|
+
@cookie = ""
|
17
|
+
@uri = URI(new_url)
|
18
|
+
end
|
19
|
+
|
20
|
+
def session
|
21
|
+
Net::HTTP.start(@uri.host, @uri.port, use_ssl: @uri.scheme == 'https')
|
22
|
+
rescue Errno::ECONNREFUSED => e
|
23
|
+
sleep(1); retry
|
24
|
+
end
|
25
|
+
|
26
|
+
def get path, query=''
|
27
|
+
path = path[0...-1] if path[-1] == "/"
|
28
|
+
path = "/#{path}" unless path[0] == "/"
|
29
|
+
if query == ''
|
30
|
+
_get path
|
31
|
+
else
|
32
|
+
_get "#{path}?#{to_query(query)}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def _get path, limit = 10
|
37
|
+
raise ArgumentError, 'too many HTTP redirects' if limit == 0
|
38
|
+
res = session.get(path, {'Cookie'=>@cookie})
|
39
|
+
cookie_array = res.get_fields('set-cookie')
|
40
|
+
@cookie = cookie_array.collect{|ea| ea[/^.*?;/]}.join if cookie_array
|
41
|
+
final_res = handle_res res, limit
|
42
|
+
JSON.parse final_res
|
43
|
+
rescue JSON::ParserError
|
44
|
+
final_res
|
45
|
+
end
|
46
|
+
|
47
|
+
def post path, params
|
48
|
+
res = session.post path, to_query(params), {'Cookie'=>@cookie}
|
49
|
+
handle_res res
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_query params, namespace=nil
|
53
|
+
if params.is_a?(Hash)
|
54
|
+
params.collect do |key, value|
|
55
|
+
to_query(value, namespace ? "#{namespace}[#{key}]" : key)
|
56
|
+
end.sort * '&'
|
57
|
+
elsif params.is_a?(Array)
|
58
|
+
prefix = "#{namespace}[]"
|
59
|
+
params.collect { |value| to_query(value, prefix) }.join '&'
|
60
|
+
else
|
61
|
+
"#{CGI.escape(namespace.to_s)}=#{CGI.escape(params.to_s)}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def handle_res res, limit=10
|
66
|
+
case res
|
67
|
+
when Net::HTTPRedirection
|
68
|
+
location = res['location']
|
69
|
+
warn "redirected to #{location}"
|
70
|
+
if %r|^http://.+$| =~ location || %r|^https://.+$| =~ location
|
71
|
+
@uri = URI(location)
|
72
|
+
_get(@uri.path, limit - 1)
|
73
|
+
else
|
74
|
+
_get(location, limit - 1)
|
75
|
+
end
|
76
|
+
when Net::HTTPSuccess
|
77
|
+
res.body
|
78
|
+
else
|
79
|
+
res.value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'itg_test/http_client'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Declarative
|
5
|
+
def test name, &block
|
6
|
+
test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
|
7
|
+
define_method test_name, &block
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ItgTest; end
|
12
|
+
class ItgTest::TestCase
|
13
|
+
extend Declarative
|
14
|
+
def run_test
|
15
|
+
methods.select{|m| /^test_\w+$/ =~ m.to_s}.each{|m| send(m); print "."}
|
16
|
+
puts "\nFinished Tests"
|
17
|
+
rescue Exception => e
|
18
|
+
puts "#{e.class}: #{e}"
|
19
|
+
puts e.backtrace
|
20
|
+
ensure
|
21
|
+
wait_exit
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize url, pid
|
25
|
+
@pid = pid
|
26
|
+
@client = ItgTest::HTTPClient.new url
|
27
|
+
end
|
28
|
+
|
29
|
+
def post path, data
|
30
|
+
@response = @client.post path, data
|
31
|
+
end
|
32
|
+
|
33
|
+
def get path
|
34
|
+
@response = @client.get path
|
35
|
+
end
|
36
|
+
|
37
|
+
def assert_difference expression, difference=1
|
38
|
+
exs = [expression].flatten
|
39
|
+
before = exs.map{|e| eval e}
|
40
|
+
yield
|
41
|
+
exs.each_with_index{ |e, i| assert_equal(before[i] + difference, eval(e), e) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def assert_select selector, content
|
45
|
+
unless Nokogiri::HTML(@response).at_css(selector).try(:content) == content
|
46
|
+
raise "#{content} doesn't match #{selector} of\n#{@response}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def assert_equal a, b, msg=""
|
51
|
+
raise "Expected #{a} == #{b} #{msg}" unless a == b
|
52
|
+
end
|
53
|
+
|
54
|
+
def wait_exit
|
55
|
+
child_pids(@pid).each{|p| Process.kill "INT", p} # kill grand_children
|
56
|
+
Process.kill "INT", @pid # kill child
|
57
|
+
Process.wait
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
|
61
|
+
def child_pids pid
|
62
|
+
pipe = IO.popen("ps -ef | grep #{pid}")
|
63
|
+
pipe.readlines.map do |line|
|
64
|
+
parts = line.split(/\s+/)
|
65
|
+
parts[2] if parts[3] == pid.to_s and parts[2] != pipe.pid.to_s
|
66
|
+
end.compact.map(&:to_i)
|
67
|
+
end
|
68
|
+
end
|
data/lib/itg_test.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module ItgTest
|
2
|
+
require 'itg_test/railtie' if defined?(Rails)
|
3
|
+
require 'itg_test/test_case'
|
4
|
+
require 'itg_test/http_client'
|
5
|
+
|
6
|
+
module_function
|
7
|
+
def absolute_file_name_to_class_name abs_fn
|
8
|
+
fn = abs_fn.match(%r|^/.*/([a-zA-Z_]+)\.rb$|)[1]
|
9
|
+
fn.split('_').map{|w| w.capitalize}.join
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
desc "integration test"
|
3
|
+
task 'test:itg' => :environment do
|
4
|
+
Rails.env = 'test'
|
5
|
+
Rake::Task['db:fixtures:load'].invoke
|
6
|
+
pid = Process.fork
|
7
|
+
if pid.nil?
|
8
|
+
exec('rails s -e test -p 3006')
|
9
|
+
exec('rails s -e test -p 3006 > /dev/null 2>&1')
|
10
|
+
else
|
11
|
+
Dir[Dir.pwd + '/test/itg/*.rb'].each {|file|
|
12
|
+
require file
|
13
|
+
Object.const_get(ItgTest.absolute_file_name_to_class_name(file)).
|
14
|
+
new('http://localhost:3006', pid).run_test
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: itg_test
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- awawfumin
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: for rails apps that are too complicated to be tested with builtin integration
|
31
|
+
test
|
32
|
+
email: fumin@cardinalblue.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- lib/itg_test.rb
|
39
|
+
- lib/itg_test/http_client.rb
|
40
|
+
- lib/itg_test/railtie.rb
|
41
|
+
- lib/itg_test/test_case.rb
|
42
|
+
- lib/tasks/test_itg.rake
|
43
|
+
- itg_test.gemspec
|
44
|
+
- README.md
|
45
|
+
homepage: http://cardinalblue.com
|
46
|
+
licenses: []
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.8.24
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: rails integration test
|
69
|
+
test_files: []
|