attest 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ vendor
23
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -0,0 +1,17 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ attest (0.1.0)
5
+ trollop
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ trollop (1.16.2)
11
+
12
+ PLATFORMS
13
+ ruby
14
+
15
+ DEPENDENCIES
16
+ attest!
17
+ trollop
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Alan Skorkin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,150 @@
1
+ = Attest
2
+
3
+ attest (vb) - to affirm the correctness or truth of
4
+
5
+ Attest allows you to define spec-like tests inline (within the same file as your actual code) which means almost non-existant overheads to putting some tests around your code. Of course that is just a feature that I wanted, you can just as easily put the tests into a separate file for a more traditional experience. Overall, attest tries to not be too prescriptive regarding the 'right' way to test. You want to test private methods - go ahead, access unexposed instance variables - no worries, pending and disabled tests are first class citizens. Don't like the output format, use a different one or write your own (at least that the plan for the future, currently there is only one output writer :P). You should be allowed to test your code the way you want to, not the way someone else says you have to!
6
+
7
+ == A Quick Example
8
+
9
+ Currently the functionality is pretty minimal, almost fully described by the example below. But as you can see the features are complete enough to make it a fully fledged testing framework. The upside is, there is not a lot to remember/learn :P. Anyways, here is an example:
10
+
11
+ class MagicCalculator
12
+ def remember_value(value)
13
+ @value_in_memory = value
14
+ end
15
+
16
+ def increment(value)
17
+ value + 1
18
+ end
19
+
20
+ def divide(numerator, denominator)
21
+ numerator/denominator
22
+ end
23
+
24
+ def add(value1, value2)
25
+ value1 + value2
26
+ end
27
+
28
+ private
29
+ def multiply(x, y)
30
+ x * y
31
+ end
32
+ end
33
+
34
+ if ENV["attest"]
35
+ this_tests MagicCalculator do
36
+ before_all{@calculator = MagicCalculator.new}
37
+ after_all{@calculator = nil}
38
+
39
+ test("a pending test")
40
+ test("deliberately fail the test"){should_fail}
41
+ test("a successful empty test"){}
42
+ test("should NOT raise an error") {should_not_raise{@calculator.increment 4}}
43
+ test("it should raise an error, don't care what kind") {should_raise {@calculator.divide 5, 0}}
44
+ test("it should raise a ZeroDivisionError error with a message"){should_raise(ZeroDivisionError){@calculator.divide 5, 0}.with_message(/divided by.*/)}
45
+ test("adding 5 and 2 does not equal 8") { should_not_be_true{ @calculator.add(5,2) == 8 } }
46
+ test("this test will be an error when non-existant method called") {should_be_true{ @calculator.non_existant }}
47
+ test("should be able to call a private method like it was public"){should_be_true{@calculator.multiply(2,2) == 4}}
48
+
49
+ test "access an instance variable without explicitly exposing it" do
50
+ @calculator.remember_value(5)
51
+ should_be_true {@calculator.value_in_memory == 5}
52
+ end
53
+
54
+ test("multiple expectations in one test") do
55
+ should_not_raise{@calculator.increment 4}
56
+ should_raise{ @calculator.non_existant }
57
+ end
58
+ test("should not have access to calculator instance since run without setup", nosetup){should_be_true{@calculator == nil}}
59
+ end
60
+ end
61
+
62
+ Here are a few things to note. All of that stuff would live in the same file and you would execute it in the following fashion:
63
+
64
+ attest -f my_ruby_file.rb
65
+
66
+ The if statement with the ENV is necessary so that the test code is comlpetely ignored unless you're running it via the 'attest' executable. If anyone can think of a better way of doing it, I am open to ideas. If you have a bunch of files that live in a directory and you want to run the test that live in all of them:
67
+
68
+ attest -d directory_with_ruby_files
69
+
70
+ As per usual, to get some help:
71
+
72
+ attest --help
73
+
74
+ If your file needs other ruby files for it to function (as they often do :)), you will need to set up the requires yourself within the 'if ENV' block. I'll give it more smarts in this area at a later date.
75
+
76
+ If you want to put the tests in a separate file, you can do that, but you will still need to wrap them in an 'if ENV' block and set up the 'requires' yourself as well, once again more smarts will hopefully appear at a later date.
77
+
78
+ Currently the output produces when running will look something like this:
79
+
80
+ >~/projects/attest$ bin/attest -f examples/magic_calculator.rb
81
+ /home/yellow/projects/attest/examples/magic_calculator.rb:
82
+ MagicCalculator
83
+ - a pending test [PENDING]
84
+ - deliberately fail the test [FAILURE]
85
+ - a successful empty test
86
+ - should NOT raise an error
87
+ - it should raise an error, don't care what kind
88
+ - it should raise a ZeroDivisionError error with a message
89
+ - adding 5 and 2 does not equal 8
90
+ - this test will be an error when non-existant method called [ERROR]
91
+
92
+ NoMethodError: undefined method `non_existant' for #<MagicCalculator:0x00000003097b60>
93
+
94
+ - should be able to call a private method like it was public
95
+ - access an instance variable without explicitly exposing it
96
+ - multiple expectations in one test
97
+ - should not have access to calculator instance since run without setup
98
+
99
+
100
+ Ran 12 tests: 9 successful, 1 failed, 1 errors, 1 pending
101
+
102
+ Currently that's the only kind of output format, I'll whip up some more output formats at a later date and/or give ability to supply your own.
103
+
104
+ == Current And Upcoming Features
105
+
106
+ - define tests inline
107
+ - call private methods as if they were public
108
+ - access instance variables as if they were exposed
109
+ - a basic output writer
110
+ - only a few expectation types, should_fail, should_be_true, should_not_be_true, should_raise, should_not_raise - that's it for the moment
111
+ - setup and teardown for all tests, but can connfigure a test so that no setup is run for it
112
+ - automatically create a class that includes a module which methods you want to test (if you have a module called MyModule and you want to test its methods buy only after the module has been included in a class, a class called MyModuleClass will be automatically created and will include your module, an object of this new class will be instantiated for you to play with) e.g:
113
+
114
+ before_all do
115
+ @module_class = create_and_include(MyModule)
116
+ end
117
+
118
+ - tries not to pollute core objects too much
119
+
120
+ I've got a few things I want to try out in the future, I've already mentioned some of them such as more output writers and ability to define your own as well as giving it more smarts in various areas. More specifically here are some things that are high on my radar:
121
+
122
+ - integrating with Mocha to provide at least some test double (i.e. mocking/stubbing) functionality
123
+ - writing my own test double project in the same general style as attest and integrating with it as well to give another more seamless mock/stub option
124
+ - default rake task for ability to run via rake rather than having to use the attest executable
125
+ - output the expectation count, not just the test count in the output report
126
+ - more smarts around classes that define their own method missing
127
+ - ability to disable tests and have the output list them as such
128
+ - write some basic unit tests for the framework using itself as the test framework (i.e. eating own dogfood style)
129
+ - allow for multiple setup and teardown blocks and ability to specify which tests they are relevant for
130
+ - haven't yet decided if I nested contexts are a good idea, I find they tend to confuse things
131
+ - maybe ability to do shared contexts, once again haven't decided if they are a good idea
132
+ - more types of expectations for convenience, e.g. should_equal etc.
133
+ - look at providing convenience stuff for web testing since that seems to be a big deal these days, ditto file system testing (requires mucho more thought)
134
+
135
+
136
+ == More Examples
137
+
138
+ Go to the examples directory in the code, it contains the above example as well as a couple of others, reasonably easy to understand, as I said not a lot to remember at the moment.
139
+
140
+ == Note on Patches/Pull Requests
141
+
142
+ * Fork the project.
143
+ * Make your feature addition or bug fix.
144
+ * Add tests for it. This is important so I don't break it in a future version unintentionally. (Ok, you can't really do that as yet, but it's coming up :))
145
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
146
+ * Send me a pull request. Bonus points for topic branches.
147
+
148
+ == Copyright
149
+
150
+ Copyright (c) 2010 Alan Skorkin. See LICENSE for details.
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "attest"
8
+ gem.summary = %Q{An inline unit testing/spec framework that doesn't force you to follow arbitrary rules}
9
+ gem.description = %Q{Attest allows you to define spec-like tests inline (within the same file as your actual code) which means almost non-existant overheads to putting some tests around your code. It also tries to not be too prescriptive regarding the 'right' way to test. You want to test private methods - go ahead, access unexposed instance variables - no worries, pending and disabled tests are first class citizens. Don't like the output format, use a different one or write your own. Infact you don't even have to define you tests inline if you prefer the 'traditional' way, separate directory and all. You should be allowed to test your code the way you want to, not the way someone else says you have to!}
10
+ gem.email = "alan@skorks.com"
11
+ gem.homepage = "http://github.com/skorks/attest"
12
+ gem.authors = ["Alan Skorkin"]
13
+ gem.add_runtime_dependency "trollop"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "attest #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,73 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{attest}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Alan Skorkin"]
12
+ s.date = %q{2010-11-29}
13
+ s.default_executable = %q{attest}
14
+ s.description = %q{Attest allows you to define spec-like tests inline (within the same file as your actual code) which means almost non-existant overheads to putting some tests around your code. It also tries to not be too prescriptive regarding the 'right' way to test. You want to test private methods - go ahead, access unexposed instance variables - no worries, pending and disabled tests are first class citizens. Don't like the output format, use a different one or write your own. Infact you don't even have to define you tests inline if you prefer the 'traditional' way, separate directory and all. You should be allowed to test your code the way you want to, not the way someone else says you have to!}
15
+ s.email = %q{alan@skorks.com}
16
+ s.executables = ["attest"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "Gemfile",
25
+ "Gemfile.lock",
26
+ "LICENSE",
27
+ "README.rdoc",
28
+ "Rakefile",
29
+ "VERSION",
30
+ "attest.gemspec",
31
+ "bin/attest",
32
+ "doodle.txt",
33
+ "examples/magic_calculator.rb",
34
+ "examples/more/placeholder.rb",
35
+ "examples/standard_calculator.rb",
36
+ "lib/attest.rb",
37
+ "lib/attest/attest_error.rb",
38
+ "lib/attest/config.rb",
39
+ "lib/attest/core_ext/kernel.rb",
40
+ "lib/attest/core_ext/object.rb",
41
+ "lib/attest/execution_context.rb",
42
+ "lib/attest/expectation_result.rb",
43
+ "lib/attest/itself.rb",
44
+ "lib/attest/output/basic_output_writer.rb",
45
+ "lib/attest/test_container.rb",
46
+ "lib/attest/test_object.rb",
47
+ "lib/attest/test_parser.rb"
48
+ ]
49
+ s.homepage = %q{http://github.com/skorks/attest}
50
+ s.rdoc_options = ["--charset=UTF-8"]
51
+ s.require_paths = ["lib"]
52
+ s.rubygems_version = %q{1.3.7}
53
+ s.summary = %q{An inline unit testing/spec framework that doesn't force you to follow arbitrary rules}
54
+ s.test_files = [
55
+ "examples/more/placeholder.rb",
56
+ "examples/magic_calculator.rb",
57
+ "examples/standard_calculator.rb"
58
+ ]
59
+
60
+ if s.respond_to? :specification_version then
61
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
62
+ s.specification_version = 3
63
+
64
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
65
+ s.add_runtime_dependency(%q<trollop>, [">= 0"])
66
+ else
67
+ s.add_dependency(%q<trollop>, [">= 0"])
68
+ end
69
+ else
70
+ s.add_dependency(%q<trollop>, [">= 0"])
71
+ end
72
+ end
73
+
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #need this so that bundler doesn't throw an error after library has been installed as gem and the executable is called
4
+ ENV["BUNDLE_GEMFILE"] = File.expand_path(File.dirname(__FILE__) + "/../Gemfile")
5
+ ENV["attest"] = "true"
6
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/attest")
7
+
8
+ opts = Trollop::options do
9
+ banner <<-EOS
10
+ Usage:
11
+ attest [options] <filenames>+
12
+ where [options] are:
13
+ EOS
14
+
15
+ opt :file, "Ruby file with inline tests that should be executed", :type => String
16
+ opt :directory, "Directory with ruby files which contain inline tests to be executed", :type => String, :default => nil
17
+ end
18
+ Trollop::die :file, "Must exist" unless File.exist?(opts[:file]) if opts[:file]
19
+ Trollop::die :directory, "Must exist" unless File.exist?(opts[:directory]) && File.directory?(opts[:directory]) if opts[:directory]
20
+
21
+ Attest.configure do |config|
22
+ config.output_writer = Attest::Output::BasicOutputWriter.new
23
+ end
24
+
25
+ if opts[:file]
26
+ Attest.config.current_file = File.join(File.expand_path(opts[:file]))
27
+ load opts[:file]
28
+ end
29
+
30
+ if opts[:directory]
31
+ Dir[File.join(File.expand_path(opts[:directory]), "**/*.rb")].each do |file|
32
+ Attest.config.current_file = file
33
+ load file
34
+ end
35
+ end
36
+
37
+ Attest.output_writer.summary
@@ -0,0 +1,142 @@
1
+ rough features
2
+
3
+ able to write single test in the same file as code under test, should not be able to accidentally run the test code
4
+ able to go through all the ruby files in the project and run any tests that are found
5
+ able to print progress to the commandline as tests are being run
6
+ do not pollute the core classes unnecessarily
7
+
8
+ able to go through all the ruby files in supplied directory running tests when found
9
+
10
+
11
+
12
+
13
+
14
+ passing a ruby file to attest should print out whether or not there may be any inline tests in the ruby file
15
+
16
+
17
+ most basic
18
+ - setup and teardown DONE
19
+ - specify test for regular public method DONE
20
+ - specify test for a private method DONE
21
+ - ability to read instance variable of object under test without having to specify a reader DONE
22
+ - the most basic result output DONE
23
+ - able to detect when test expectation fails DONE
24
+ - execute tests for single ruby file if any present DONE
25
+ - able to detect when test errors out and gracefully say so in output DONE
26
+ - all object to have a should_equal expectation method DONE
27
+ - all objects should have a should_not_equal expectation method DONE
28
+
29
+ - should be able to specify a test anonymously without providing description
30
+ - ability to give a test an id as well as description
31
+ - ability to specify that no setup or teardown should be run for the test
32
+ - ability to specify multiple setups and teardowns and register specific setups and teardowns to run for specific tests
33
+ - ability to magically include module in an object and test its methods while it is included in an object
34
+ - should provide the error message if test errors out DONE
35
+ - should provide the error trace if a test has errored DONE
36
+ - all objects have access to a should_raise expectation DONE
37
+ - should be able to produce a summary of execution at the end, with count of successes, failures and errors DONE
38
+ - should be able to produce shorthand output while running with a summary at the end (test unit style shorthand), controlled by cli parameter
39
+ - should have a should_fail expectation, possibly for tests that haven't been implemented aliased to not_implemented, not_implemented_failing, not_implemented_passing
40
+ - should have a should_be_true expectation type for more flexibility DONE
41
+ - test that are defined without a block should be tagged as not implemented in the output and in the summary
42
+ - you can require the library without augmenting objects with extra methods, only when you call do objects get augmented and should test for the fact that a method we're trying to augment with already exists, alternatively, don't ever require this library directly then we don't have an issue DONE
43
+
44
+ - the should methods should return an expectation object (itself) DONE
45
+ - when deliberately failing should not actually print an error but should be a failure with a message instead DONE
46
+ - the output writer should be a separate class to allow for different writers DONE
47
+ - work out what methods need to be on the output writer class and hook them in where tests are run DONE
48
+ - work out what other expectations need to be on the execution context intself (should_raise, should_be_true) DONE
49
+ - work out how to easily implement negation of the positive expectations on the execution context
50
+ - hook in configuration into the framework DONE
51
+ - make it configurable via configuration and command line the name of the method that begins a test context (this_tests)
52
+ - make it configurable via config and command line the name of the method that would produce an expectation object (itself or should), these are the only methods that extend core for now
53
+ - try to execute existing method missing and if still an error then execute my method missing and fall back to existing method missing, so that don't accidentally kill method missing functionality that people wrote themselves
54
+ - produce a short format output writer as well as a basic long format one to make sure that have ability to hook in different output writers
55
+ - should be able to configure attest via file (attest.config in same directory, or give location of config as command line param, or via .attest file in home dir or via a configuration block in a file under test, each level can override the other)
56
+ - what expectation methods are actaully needed on the objects in the test methods themselves (should_be_true, should_equal etc) work out negation for these ones as well
57
+ - what methods should the expectation object have and how to allow to chain the methods and have everything work
58
+ - all test context should have an id and if one is not provided it should be generated
59
+ - all test methods should have an id and if one is not provided it should be generated
60
+ - before and after blocks should be able to refer to tests they are targeting by the test id
61
+ - test should be able to specify if they don't want the befores and afters to be run for them (this will take precedence over everything else)
62
+ - need to have a should_fail execution context method, with alias of fail
63
+ - should be able to call should_be empty on an object where empty is a method on the object, should_equal size 5 where size is a method on the object, i.e. boolean method on the objects gets used as a predicate
64
+ - a expectation that this object as the same as another object, i.e. exactly the same object not an equal one
65
+ - for exceptions, should_raise(XyzError) {x.some_call}.with_message(/some message regex/), the XyzError and the regex should be optional DONE
66
+ - test methods in a module that is built for class inclusion
67
+ - test free floating methods that don't belong to a class or a module
68
+ - an anonymous test without description similat to the test pile below but still for only one thing
69
+ - a test pile method where you want to test random methods or do lots of assertions at the same time without having to think of a name for it, should introspect somehow what is/will be run and work out descriptions from that
70
+ - ability to define matchers as lambdas would be good i.e. obj.should_be happy where happy is a lambda that returns a boolean value
71
+
72
+
73
+
74
+ - a rake task to run the tests instead of a separate executable, configuration should be done through the rake task
75
+ - ability to mock stuff out using some of the popular mocking frameworks, ability to plug in different one by config, including my own by default which hasn't been written yet
76
+ - potentially later on think about doing nested contexts as well as shared contexts
77
+ - some way to potentially work out if files are ruby even if no .rb extension or alternatively pass a glob on the command line to match files
78
+ - allow redefining of the name of the main context method through the command line(e.g. rather than this_tests allow person to name it whatever they want)
79
+
80
+
81
+ - kill the This class and turn it back into a this_tests method on Kernel DONE
82
+ - merge everything into Master DONE
83
+ - branch master into a Hooks branch and leave to have a look at later DONE
84
+ - remove the hooks stuff from master DONE
85
+ - add the file in which the tests were found to the output of the tests DONE
86
+ - add functionality to allow a directory with ruby files to be parsed for tests and all tests to be run if any are found, non-recursively DONE
87
+ - add functionality to allow a directory to be parsed for tests recursively DONE
88
+ - allow multiple context within the same file to work properly and execute properly DONE
89
+ - make sure can test methods in a module not just in a class, magically included in a class DONE
90
+ - make sure can test class methods DONE
91
+ - make sure can test methods that don't belong to a module or a class DONE
92
+ - when multiple contexts in the same file, only print the name of the file to the command line once DONE
93
+ - don't print summaries after every context, only a totals summary at the end DONE
94
+ - make it so that tests can take an options hash and one of the options should be to not run setup or teardown for the test, perhaps make it not part of the opts hash DONE
95
+ - should be able to specify a test without a body for it to appear in the output as pending DONE
96
+ - refactor all example code so it is a bit nicer and more representative
97
+ - once all the above are done, create some RDOC documentation to describe the basic features and usage of the framework
98
+ - also fix up the description and extended description, make sure everything looks proper as far as doco is concerned on github etc.
99
+ - if everything is ok at this point, do a first public release of the gem (give it an appropriately betaish version number)
100
+ - make sure the gem is available through rubyforge as well as wherever else is appropriate, e.g. gemcutter
101
+
102
+
103
+ - when committing need to make sure there is a valid gemspec
104
+ - when used with bundler dependencies like trollop don't come trhough so can't execute attest properly get an error instead need to detect if being used via bundler and do a bundle setup etc.
105
+
106
+ next release
107
+
108
+ - integrate mocha so that can mock using mocha
109
+ - output calculations of expectations not just tests
110
+ - allow expectations to be specified directly as tests (perhaps failures should contain the block that has failed)
111
+ - create a failures only output writer and allow it to be configured
112
+ - create a test/unit style output writer and allow it to be configured
113
+ - test out with method missing defined on the class, does it still work ok
114
+ - make sure if method missing is defined it is tried out first before the new_method_missing on kernel takes over
115
+ - should be able to disable a test so that it is not executed, but is listed as disabled in the output
116
+
117
+ next release 2
118
+
119
+ - write and integrate own test double framework (look at rr for syntax suggestions)
120
+ - create some tests using the framework itself around the framework classes (minimal test suite)
121
+ - allow expectations to be made on objects themselves
122
+ - allow for tests to be given ids as well as descriptions
123
+ - allow for multiple before and after blocks should specify which test to run this block for using ids of tests as parameters
124
+
125
+ next release 3
126
+ - come up with some more useful expectations, both on object and as standalone methods of test
127
+ - look into rails stuff regarding expectations and idioms etc
128
+ - should somehow be able to require all the necessary ruby files that are needed when a test is run for the test not to fail, e.g. because ruby files depend on other ruby files
129
+
130
+
131
+
132
+ what to say in readme
133
+ give an example of usage
134
+ talk about how to define tests inline
135
+ talk about how to execute the tests in one file or directory etc
136
+ list the currently supported expectations
137
+ talk about pendind tests
138
+ talk about the fact that other output writers are coming
139
+ talk about the fact that you can disable setup for particular tests
140
+ talk about how to test modules within an included class
141
+ no nested context as yet, but can define multiple top level ones, no shared tests or contexts as yet
142
+ mention the examples directory but it is currently crappy
@@ -0,0 +1,50 @@
1
+ class MagicCalculator
2
+ def remember_value(value)
3
+ @value_in_memory = value
4
+ end
5
+
6
+ def increment(value)
7
+ value + 1
8
+ end
9
+
10
+ def divide(numerator, denominator)
11
+ numerator/denominator
12
+ end
13
+
14
+ def add(value1, value2)
15
+ value1 + value2
16
+ end
17
+
18
+ private
19
+ def multiply(x, y)
20
+ x * y
21
+ end
22
+ end
23
+
24
+ if ENV["attest"]
25
+ this_tests MagicCalculator do
26
+ before_all{@calculator = MagicCalculator.new}
27
+ after_all{@calculator = nil}
28
+
29
+ test("a pending test")
30
+ test("deliberately fail the test"){should_fail}
31
+ test("a successful empty test"){}
32
+ test("should NOT raise an error") {should_not_raise{@calculator.increment 4}}
33
+ test("it should raise an error, don't care what kind") {should_raise {@calculator.divide 5, 0}}
34
+ test("it should raise a ZeroDivisionError error with a message"){should_raise(ZeroDivisionError){@calculator.divide 5, 0}.with_message(/divided by.*/)}
35
+ test("adding 5 and 2 does not equal 8") { should_not_be_true{ @calculator.add(5,2) == 8 } }
36
+ test("this test will be an error when non-existant method called") {should_be_true{ @calculator.non_existant }}
37
+ test("should be able to call a private method like it was public"){should_be_true{@calculator.multiply(2,2) == 4}}
38
+
39
+ test "access an instance variable without explicitly exposing it" do
40
+ @calculator.remember_value(5)
41
+ should_be_true {@calculator.value_in_memory == 5}
42
+ end
43
+
44
+ test("multiple expectations in one test") do
45
+ should_not_raise{@calculator.increment 4}
46
+ should_raise{ @calculator.non_existant }
47
+ end
48
+ test("should not have access to calculator instance since run without setup", nosetup){should_be_true{@calculator == nil}}
49
+ end
50
+ end
@@ -0,0 +1,28 @@
1
+ class Placeholder1
2
+ def divide(x,y)
3
+ x/y
4
+ end
5
+ end
6
+
7
+ class Placeholder2
8
+ def multiply(x,y)
9
+ x*y
10
+ end
11
+ end
12
+
13
+ def random_method(x)
14
+ x+x
15
+ end
16
+
17
+ if ENV["attest"]
18
+ this_tests Placeholder1 do
19
+ test("divide successful"){should_be_true{Placeholder1.new.divide(16, 4) == 4}}
20
+ test("divide by zero"){should_raise{Placeholder1.new.divide(5,0)}}
21
+ end
22
+ this_tests Placeholder2 do
23
+ test("multiply") {should_be_true{Placeholder2.new.multiply(2,3)==6}}
24
+ end
25
+ this_tests "random methods" do
26
+ test("random method"){should_be_true{random_method("abc") == "abcabc"}}
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ class StandardCalculator
2
+ def self.plus(x, y)
3
+ x + y
4
+ end
5
+
6
+ def self.minus(x, y)
7
+ x - y
8
+ end
9
+ end
10
+
11
+ module CalcMethods
12
+ def double(x)
13
+ 2 * x
14
+ end
15
+ end
16
+
17
+ if ENV["attest"]
18
+ this_tests "another class with claculations" do
19
+ test("adding two numbers") {should_be_true{StandardCalculator.plus(5,11) == 16}}
20
+ test("subtracting two numbers"){should_not_be_true{StandardCalculator.minus(10,5) == 4}}
21
+ end
22
+
23
+ this_tests CalcMethods do
24
+ before_all { @module_class = create_and_include(CalcMethods) }
25
+
26
+ test("magically instance of a class that will include the module"){should_be_true{@module_class.double(5)==10}}
27
+ end
28
+ end
@@ -0,0 +1,38 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.setup(:default)
4
+
5
+ require "trollop"
6
+ require "singleton"
7
+ require 'attest/config'
8
+ require 'attest/core_ext/kernel'
9
+ require 'attest/core_ext/object'
10
+
11
+ require 'attest/expectation_result'
12
+ require 'attest/test_container'
13
+ require 'attest/test_object'
14
+ require 'attest/execution_context'
15
+ require 'attest/test_parser'
16
+ require 'attest/itself'
17
+ require 'attest/attest_error'
18
+
19
+ require 'attest/output/basic_output_writer'
20
+
21
+ module Attest
22
+ class << self
23
+ def configure
24
+ config = Attest::Config.instance
25
+ block_given? ? yield(config) : config
26
+ end
27
+
28
+ alias :config :configure
29
+
30
+ Attest::Config.public_instance_methods(false).each do |name|
31
+ self.class_eval <<-EOT
32
+ def #{name}(*args)
33
+ configure.send("#{name}", *args)
34
+ end
35
+ EOT
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,7 @@
1
+ module Attest
2
+ class AttestError < RuntimeError
3
+ def initialize(message)
4
+ super message
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ module Attest
2
+ class Config
3
+ include Singleton
4
+
5
+ attr_accessor :output_writer, :current_file
6
+
7
+ def initialize
8
+ @output_writer = Attest::Output::BasicOutputWriter.new
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,34 @@
1
+ module Kernel
2
+ def new_method_missing(name, *args, &block)
3
+ private_method = false
4
+ instance_variable = false
5
+ private_methods.each do |meth|
6
+ private_method = true if meth == name
7
+ end
8
+ instance_variable = true if instance_variable_defined?("@#{name}".to_sym)
9
+ if private_method
10
+ send(name, *args, &block)
11
+ elsif instance_variable
12
+ self.class.class_eval do
13
+ attr_reader name.to_sym
14
+ end
15
+ send(name, *args, &block)
16
+ else
17
+ old_method_missing(name, *args, &block)
18
+ end
19
+ end
20
+ alias_method :old_method_missing, :method_missing
21
+ alias_method :method_missing, :new_method_missing
22
+
23
+ private
24
+ def this_tests(description="anonymous", &block)
25
+ container = Attest::TestParser.new(description, block).parse
26
+ container.execute_all
27
+ end
28
+ def current_method
29
+ caller[0][/`([^']*)'/, 1]
30
+ end
31
+ def calling_method
32
+ caller[1][/`([^']*)'/, 1]
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ class Object
2
+ def itself
3
+ Attest::Itself.new(self)
4
+ end
5
+ end
@@ -0,0 +1,84 @@
1
+ module Attest
2
+ class ExecutionContext
3
+ attr_reader :results
4
+
5
+ def initialize
6
+ @results = []
7
+ @subject = self
8
+ end
9
+
10
+ def should_raise(type=nil, &block)
11
+ result = Attest::ExpectationResult.new
12
+ begin
13
+ if block_given?
14
+ yield
15
+ end
16
+ rescue => e
17
+ result.update(:expected_error => e)
18
+ if type && type == e.class
19
+ result.success
20
+ else
21
+ result.success
22
+ end
23
+ end
24
+ unless result.success?
25
+ result.failure
26
+ end
27
+ @results << result
28
+ self
29
+ end
30
+
31
+ def with_message(regex)
32
+ result = @results.last
33
+ if result.success? && result.attributes[:expected_error]
34
+ if !(result.attributes[:expected_error].message =~ regex)
35
+ result.failure
36
+ end
37
+ end
38
+ self
39
+ end
40
+
41
+ def should_fail
42
+ result = Attest::ExpectationResult.new
43
+ result.failure
44
+ @results << result
45
+ self
46
+ end
47
+
48
+ def should_be_true(&block)
49
+ result = Attest::ExpectationResult.new
50
+ block_return = yield
51
+ block_return ? result.success : result.failure
52
+ @results << result
53
+ self
54
+ end
55
+
56
+ def should_not_raise(&block)
57
+ should_raise(&block)
58
+ result = @results.last
59
+ result.success? ? result.failure : result.success
60
+ self
61
+ end
62
+
63
+ def should_not_be_true(&block)
64
+ should_be_true(&block)
65
+ result = @results.last
66
+ result.success? ? result.failure : result.success
67
+ self
68
+ end
69
+
70
+ #worker methods
71
+ def create_and_include(module_class)
72
+ class_name = "#{module_class}Class"
73
+ class_instance = Class.new
74
+ Object.const_set class_name, class_instance
75
+ Object.const_get(class_name).include(Object.const_get("#{module_class}"))
76
+ klass = Object.const_get(class_name)
77
+ klass.new
78
+ end
79
+
80
+ def nosetup
81
+ true
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,28 @@
1
+ module Attest
2
+ class ExpectationResult
3
+ attr_reader :attributes
4
+ def initialize(attributes={})
5
+ @outcome = nil
6
+ @attributes = attributes
7
+ end
8
+
9
+ [:success, :failure, :error, :pending].each do |status|
10
+ eval <<-EOT
11
+ def #{status}
12
+ @outcome = current_method
13
+ end
14
+ def #{status}?
15
+ current_method.chop == @outcome
16
+ end
17
+ EOT
18
+ end
19
+
20
+ def status
21
+ @outcome
22
+ end
23
+
24
+ def update(attributes={})
25
+ @attributes.merge!(attributes)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ module Attest
2
+ class Itself
3
+
4
+ def initialize(object)
5
+ @itself = object
6
+ @state = state
7
+ end
8
+
9
+ #when error is set, if it is a child of an exception or is an exception itself then with_message is available, otherwise it is not and shoudl throw a no method
10
+
11
+ #def with_message(regex)
12
+ #raise NoMethodError if !@itself.kind_of?(Exception)
13
+ #if @itself.message =~ regex
14
+ #@state = Attest::Itself.STATES[:success]
15
+ #end
16
+ #end
17
+
18
+ #def should_equal(another_object)
19
+ #@itself == another_object
20
+ #end
21
+
22
+ #def should_not_equal(another_object)
23
+ #@itself != another_object
24
+ #end
25
+
26
+ #should_be_true
27
+ #should_not_be_true
28
+ #should_equal
29
+ #should_not_equal
30
+ #should_be_same
31
+ #should_not_be_same
32
+ #with_message if itself is an error object
33
+ end
34
+ end
@@ -0,0 +1,73 @@
1
+ module Attest
2
+ module Output
3
+ class BasicOutputWriter
4
+ def initialize
5
+ @containers = []
6
+ end
7
+
8
+ def before_everything
9
+ end
10
+
11
+ def before_context(container)
12
+ previous_container = @containers.last
13
+ @containers << container
14
+ puts "#{container.file}:" unless previous_container && previous_container.file == container.file
15
+ puts " #{ container.description }"
16
+ end
17
+
18
+ def before_test(test_object)
19
+ print " - #{test_object.description}"
20
+ end
21
+
22
+ def after_test(test_object)
23
+ relevant_result = nil
24
+ test_object.results.each do |result|
25
+ relevant_result = result if !result.success?
26
+ end
27
+ print " [#{relevant_result.status.upcase}]" if relevant_result
28
+ if relevant_result && relevant_result.error?
29
+ e = relevant_result.attributes[:unexpected_error]
30
+ 2.times { puts }
31
+ puts " #{e.class}: #{e.message}"
32
+ e.backtrace.each do |line|
33
+ break if line =~ /lib\/attest/
34
+ puts " #{line} "
35
+ end
36
+ end
37
+ puts
38
+ end
39
+
40
+ def after_context
41
+ puts
42
+ end
43
+
44
+ def summary
45
+ return unless @containers.size >= 1
46
+ tests, success, failure, error, pending = 0, 0, 0, 0, 0
47
+ @containers.each do |container|
48
+ container.test_objects.each do |test_object|
49
+ tests += 1
50
+ test_object.results.each do |result|
51
+ if result.success?
52
+ success += 1
53
+ elsif result.failure?
54
+ failure += 1
55
+ elsif result.error?
56
+ error += 1
57
+ elsif result.pending?
58
+ pending += 1
59
+ else
60
+ raise "Errr, WTF!!!"
61
+ end
62
+ end
63
+ end
64
+ end
65
+ puts
66
+ puts "Ran #{tests} tests: #{success} successful, #{failure} failed, #{error} errors, #{pending} pending"
67
+ end
68
+
69
+ def after_everything
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,24 @@
1
+ module Attest
2
+ class TestContainer
3
+
4
+ attr_reader :description, :test_objects, :file
5
+
6
+ def initialize(description)
7
+ @file = Attest.current_file
8
+ @description = description
9
+ @test_objects = []
10
+ end
11
+
12
+ def add(test)
13
+ @test_objects << test
14
+ end
15
+
16
+ def execute_all
17
+ Attest.output_writer.before_context(self)
18
+ @test_objects.each do |test_object|
19
+ test_object.run
20
+ end
21
+ Attest.output_writer.after_context
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,59 @@
1
+ module Attest
2
+ class TestObject
3
+ attr_reader :description, :results
4
+ attr_accessor :nosetup
5
+ def initialize(description, test_block)
6
+ @description = description
7
+ @test_block = test_block
8
+ @before = nil
9
+ @after = nil
10
+ @results = nil
11
+ end
12
+
13
+ def add_setup(block)
14
+ @before = block
15
+ end
16
+
17
+ def add_cleanup(block)
18
+ @after = block
19
+ end
20
+
21
+ def run
22
+ Attest.output_writer.before_test(self)
23
+ error = nil
24
+ context = Attest::ExecutionContext.new
25
+ begin
26
+ Object.class_eval do
27
+ define_method :itself do
28
+ subject = self
29
+ context.instance_eval {@subject = subject}
30
+ context
31
+ end
32
+ end
33
+ context.instance_eval(&@before) if @before && !nosetup
34
+ context.instance_eval(&@test_block) if @test_block
35
+ context.instance_eval(&@after) if @after && !nosetup
36
+ rescue => e
37
+ error = e
38
+ ensure
39
+ @results = context.results
40
+ add_unexpected_error_result(error) if error
41
+ add_pending_result unless @test_block
42
+ end
43
+ Attest.output_writer.after_test(self)
44
+ end
45
+
46
+ private
47
+ def add_unexpected_error_result(error)
48
+ result = Attest::ExpectationResult.new(:unexpected_error => error)
49
+ result.error
50
+ @results << result
51
+ end
52
+
53
+ def add_pending_result
54
+ result = Attest::ExpectationResult.new
55
+ result.pending
56
+ @results << result
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,38 @@
1
+ module Attest
2
+ class TestParser
3
+ def initialize(description, block)
4
+ @description = description
5
+ @block = block
6
+ @before = nil
7
+ @after = nil
8
+ @tests = {}
9
+ @nosetup = {}
10
+ end
11
+
12
+ def parse
13
+ self.instance_eval(&@block)
14
+ test_container = Attest::TestContainer.new(@description)
15
+ @tests.each_pair do |description, test_block|
16
+ test_object = TestObject.new(description, test_block)
17
+ test_object.nosetup = true if @nosetup[description]
18
+ test_object.add_setup(@before)
19
+ test_object.add_cleanup(@after)
20
+ test_container.add(test_object)
21
+ end
22
+ test_container
23
+ end
24
+
25
+ def before_all(&block)
26
+ @before = block
27
+ end
28
+
29
+ def after_all(&block)
30
+ @after = block
31
+ end
32
+
33
+ def test(description, nosetup=false, &block)
34
+ @tests[description] = block
35
+ @nosetup[description] = true if nosetup
36
+ end
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: attest
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Alan Skorkin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-29 00:00:00 +11:00
19
+ default_executable: attest
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: trollop
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Attest allows you to define spec-like tests inline (within the same file as your actual code) which means almost non-existant overheads to putting some tests around your code. It also tries to not be too prescriptive regarding the 'right' way to test. You want to test private methods - go ahead, access unexposed instance variables - no worries, pending and disabled tests are first class citizens. Don't like the output format, use a different one or write your own. Infact you don't even have to define you tests inline if you prefer the 'traditional' way, separate directory and all. You should be allowed to test your code the way you want to, not the way someone else says you have to!
36
+ email: alan@skorks.com
37
+ executables:
38
+ - attest
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - .document
46
+ - .gitignore
47
+ - Gemfile
48
+ - Gemfile.lock
49
+ - LICENSE
50
+ - README.rdoc
51
+ - Rakefile
52
+ - VERSION
53
+ - attest.gemspec
54
+ - bin/attest
55
+ - doodle.txt
56
+ - examples/magic_calculator.rb
57
+ - examples/more/placeholder.rb
58
+ - examples/standard_calculator.rb
59
+ - lib/attest.rb
60
+ - lib/attest/attest_error.rb
61
+ - lib/attest/config.rb
62
+ - lib/attest/core_ext/kernel.rb
63
+ - lib/attest/core_ext/object.rb
64
+ - lib/attest/execution_context.rb
65
+ - lib/attest/expectation_result.rb
66
+ - lib/attest/itself.rb
67
+ - lib/attest/output/basic_output_writer.rb
68
+ - lib/attest/test_container.rb
69
+ - lib/attest/test_object.rb
70
+ - lib/attest/test_parser.rb
71
+ has_rdoc: true
72
+ homepage: http://github.com/skorks/attest
73
+ licenses: []
74
+
75
+ post_install_message:
76
+ rdoc_options:
77
+ - --charset=UTF-8
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ requirements: []
99
+
100
+ rubyforge_project:
101
+ rubygems_version: 1.3.7
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: An inline unit testing/spec framework that doesn't force you to follow arbitrary rules
105
+ test_files:
106
+ - examples/more/placeholder.rb
107
+ - examples/magic_calculator.rb
108
+ - examples/standard_calculator.rb