itg_test 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|