-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/.crystal |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Test Unit | ||
|
||
An attempt at implementing test units in Crystal, using the fantastic | ||
[minitest](https://github.com/seattlerb/minitest) as reference. | ||
|
||
```crystal | ||
require "minitest/autorun" | ||
class MyTest < Minitest::Test | ||
def setup | ||
@var = "something" | ||
end | ||
def teardown | ||
@var = nil | ||
end | ||
def test_something | ||
p @var # => "something" | ||
end | ||
end | ||
``` | ||
|
||
## TODO | ||
|
||
- [x] keep a list of classes inheriting Minitest::Test (test suites) | ||
- [x] shuffle test suites | ||
- [x] run all test suites at exit | ||
- [x] extract the list of test methods from test suites | ||
- [ ] shuffle test methods | ||
- [x] run the test methods | ||
- [x] run setup / teardown methods before/after each test | ||
- [x] capture exceptions in setup, test or teardown | ||
- [ ] after run hooks | ||
- [ ] assertions | ||
- [ ] refutations | ||
- [ ] skip | ||
- [ ] flunk | ||
- [ ] reporter: composite (dispatches to linked reporters) | ||
- [ ] reporter: progress | ||
- [ ] reporter: verbose progress | ||
- [ ] reporter: summary | ||
|
||
## Requirements | ||
|
||
This requires Crystal >= 0.6.1. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
require "./minitest/reporter" | ||
require "./minitest/runnable" | ||
require "./minitest/test" | ||
|
||
module Minitest | ||
macro def self.run : Bool | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
asterite
Contributor
|
||
reporter = Reporter.new | ||
reporter.start | ||
|
||
Runnable.runnables.shuffle.each(&.run) | ||
|
||
reporter.report | ||
reporter.passed? | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module Minitest | ||
class Assertion < Exception | ||
end | ||
|
||
module Assertions | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
require "../minitest" | ||
|
||
def exit(code : Bool) | ||
exit code ? 0 : -1 | ||
end | ||
|
||
at_exit do | ||
exit_code = Minitest.run | ||
exit exit_code | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module Minitest | ||
class Reporter | ||
def start | ||
end | ||
|
||
def report | ||
end | ||
|
||
def passed? | ||
false | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
module Minitest | ||
class Runnable | ||
@@runnables = [Runnable] | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
ysbaddaden
Author
Owner
|
||
@@runnables.clear | ||
|
||
def self.runnables | ||
@@runnables | ||
end | ||
|
||
macro inherited | ||
Minitest::Runnable.runnables << self | ||
end | ||
|
||
macro def self.run : Nil | ||
klass = {{ (@class_name.ends_with?(":Class") ? @class_name[0..-7].id : @class_name).id }} | ||
klass.new.run_tests | ||
nil | ||
end | ||
|
||
def run_tests | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
require "./assertions" | ||
|
||
module Minitest | ||
module LifecycleHooks | ||
def before_setup | ||
end | ||
|
||
def setup | ||
end | ||
|
||
def after_setup | ||
end | ||
|
||
def before_teardown | ||
end | ||
|
||
def teardown | ||
end | ||
|
||
def after_teardown | ||
end | ||
end | ||
|
||
class UnexpectedError < Exception | ||
getter :exception | ||
|
||
def initialize(@exception) | ||
super "Unexpected error: #{exception}" | ||
end | ||
end | ||
|
||
class Test < Runnable | ||
include LifecycleHooks | ||
include Assertions | ||
|
||
# TODO: shuffle test methods | ||
macro def run_tests : Nil | ||
{{ @type.methods.map(&.name.stringify).select(&.starts_with?("test_")).map { |m| "run_one { #{m.id} }" }.join("\n").id }} | ||
nil | ||
end | ||
|
||
def run_one | ||
capture_exception do | ||
before_setup | ||
setup | ||
after_setup | ||
|
||
yield | ||
end | ||
|
||
capture_exception { before_teardown } | ||
capture_exception { teardown } | ||
capture_exception { after_teardown } | ||
end | ||
|
||
def capture_exception | ||
begin | ||
yield | ||
rescue ex : Assertion | ||
self.class.failures << ex | ||
rescue ex : Exception | ||
self.class.failures << UnexpectedError.new(ex) | ||
end | ||
end | ||
|
||
def self.failures | ||
@@failures ||= [] of Assertion | UnexpectedError | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
require "../src/minitest/autorun" | ||
|
||
class RunnableTest < Minitest::Test | ||
def setup | ||
puts "custom setup" | ||
@myvar = "instance_val" | ||
end | ||
|
||
def teardown | ||
puts "\n" | ||
end | ||
|
||
def test_something | ||
puts "something" | ||
puts "@var = #{@myvar}" | ||
end | ||
|
||
def test_something_else | ||
puts "else" | ||
puts helper | ||
end | ||
|
||
def helper | ||
"help me" | ||
end | ||
end |
@asterite there is no reason for this def to be a macro anymore (since the bug was fixed in crystal), but if I make it a plain method, then
@type.methods
inRunnable#run_tests
will only contain Runnable methods instead of the inherited class methods.It looks like with a def macro here, then #run_tests will be generated on the inherited classes (as expected), but without the def macro it will be associated to Runnable only (not expected, at least for me).
The joys of metaprogramming :-)