2018-08-18 21:33:20 +00:00
|
|
|
Spectator
|
|
|
|
=========
|
|
|
|
|
2018-09-27 20:57:58 +00:00
|
|
|
Spectator is a fully-featured spec-based test framework for Crystal.
|
|
|
|
It provides more functionality from [RSpec](http://rspec.info/)
|
2018-08-30 21:09:29 +00:00
|
|
|
than the built-in Crystal [Spec](https://crystal-lang.org/api/latest/Spec.html) utility.
|
2018-09-27 20:57:58 +00:00
|
|
|
Additionally, Spectator provides extra features to make testing easier and more fluent.
|
2018-08-18 21:33:20 +00:00
|
|
|
|
2018-11-02 20:32:36 +00:00
|
|
|
**Goal:**
|
|
|
|
|
|
|
|
Spectator is designed to:
|
|
|
|
|
|
|
|
- Reduce complexity of test code.
|
|
|
|
- Remove boilerplate from tests.
|
|
|
|
- Lower the difficulty of writing non-trivial tests.
|
|
|
|
- Provide an elegant syntax that is easy to read and understand.
|
|
|
|
- Provide common utilities that the end-user would otherwise need to write.
|
|
|
|
|
2018-08-18 21:33:20 +00:00
|
|
|
Installation
|
|
|
|
------------
|
|
|
|
|
|
|
|
Add this to your application's `shard.yml`:
|
|
|
|
|
|
|
|
```yaml
|
2018-09-27 21:02:50 +00:00
|
|
|
development_dependencies:
|
2018-08-18 21:33:20 +00:00
|
|
|
spectator:
|
|
|
|
gitlab: arctic-fox/spectator
|
|
|
|
```
|
|
|
|
|
|
|
|
Usage
|
|
|
|
-----
|
|
|
|
|
2018-08-30 21:09:29 +00:00
|
|
|
If it doesn't exist already, create a `spec/spec_helper.cr` file.
|
|
|
|
In it, place the following:
|
|
|
|
|
2018-08-18 21:33:20 +00:00
|
|
|
```crystal
|
|
|
|
require "spectator"
|
2018-08-30 21:09:29 +00:00
|
|
|
require "../src/*"
|
|
|
|
```
|
|
|
|
|
|
|
|
This will include Spectator and the source code for your shard.
|
|
|
|
Now you can start writing your specs.
|
|
|
|
The syntax is the same as what you would expect from modern RSpec.
|
|
|
|
The "expect" syntax is recommended and the default, however the "should" syntax is also available.
|
|
|
|
Your specs must be wrapped in a `Spectator.describe` block.
|
2018-09-27 21:02:50 +00:00
|
|
|
All other blocks inside the top-level block may use `describe` and `context` without the `Spectator.` prefix.
|
2018-08-30 21:09:29 +00:00
|
|
|
|
|
|
|
Here's a minimal spec to demonstrate:
|
|
|
|
|
|
|
|
```crystal
|
|
|
|
require "./spec_helper"
|
|
|
|
|
|
|
|
Spectator.describe String do
|
|
|
|
subject { "foo" }
|
|
|
|
|
|
|
|
describe "#==" do
|
|
|
|
context "with the same value" do
|
|
|
|
let(value) { subject.dup }
|
|
|
|
|
|
|
|
it "is true" do
|
|
|
|
is_expected.to eq(value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "with a different value" do
|
|
|
|
let(value) { "bar" }
|
|
|
|
|
|
|
|
it "is false" do
|
|
|
|
is_expected.to_not eq(value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2018-08-18 21:33:20 +00:00
|
|
|
```
|
|
|
|
|
2018-08-30 21:09:29 +00:00
|
|
|
If you find yourself trying to shoehorn in functionality
|
2018-09-27 21:02:50 +00:00
|
|
|
or unsure how to write a test, please create an [issue](https://gitlab.com/arctic-fox/spectator/issues/new) for it.
|
2019-03-28 02:08:06 +00:00
|
|
|
The goal is to make it as easy as possible to write specs and keep your code clean.
|
2018-11-02 20:32:36 +00:00
|
|
|
We may come up with a solution or even introduce a feature to support your needs.
|
2018-08-30 21:09:29 +00:00
|
|
|
|
2019-03-31 18:45:58 +00:00
|
|
|
**NOTE:** Due to the way this shard uses macros,
|
2018-09-27 21:02:50 +00:00
|
|
|
you may find that some code you would expect to work, or works in other spec libraries, creates syntax errors.
|
2019-03-28 02:08:06 +00:00
|
|
|
If you run into this, please create an issue so that we may try to resolve it.
|
2018-08-30 21:09:29 +00:00
|
|
|
|
|
|
|
Features
|
|
|
|
--------
|
|
|
|
|
2019-03-28 02:08:06 +00:00
|
|
|
Spectator has all of the basic functionality for BDD.
|
2019-03-31 18:45:58 +00:00
|
|
|
For full documentation on what it can do, please visit the [wiki](https://gitlab.com/arctic-fox/spectator/wikis/home).
|
2019-03-28 02:08:06 +00:00
|
|
|
|
|
|
|
### Contexts
|
|
|
|
|
|
|
|
The DSL supports arbitrarily nested contexts.
|
|
|
|
Contexts can have values defined for multiple tests (`let` and `subject`).
|
|
|
|
Additionally, hooks can be used to ensure any initialization or cleanup is done (`before`, `after`, and `around`).
|
|
|
|
Pre- and post-conditions can be used to ensure code contracts are kept.
|
|
|
|
|
2019-03-31 18:47:34 +00:00
|
|
|
```crystal
|
|
|
|
# Initialize the database before running the tests in this context.
|
|
|
|
before_all { Database.init }
|
|
|
|
|
|
|
|
# Teardown the database and cleanup after tests in the is context finish.
|
|
|
|
after_all { Database.cleanup }
|
|
|
|
|
|
|
|
# Before each test, add some rows to the database.
|
|
|
|
let(row_count) { 5 }
|
|
|
|
before_each do
|
|
|
|
row_count.times { Database.insert_row }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Remove the rows after the test to get a clean slate.
|
|
|
|
after_each { Database.clear }
|
|
|
|
|
|
|
|
describe "#row_count" do
|
|
|
|
it "returns the number of rows" do
|
|
|
|
expect(Database.row_count).to eq(row_count)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2019-03-28 02:08:06 +00:00
|
|
|
Spectator has different types of contexts to reduce boilerplate.
|
|
|
|
One is the `sample` context.
|
|
|
|
This context type repeats all tests (and contexts within) for a set of values.
|
|
|
|
For instance, some feature should behave the same for different input.
|
|
|
|
However, some inputs might cause problems, but should behave the same.
|
|
|
|
An example is various strings (empty strings, quoted strings, strings with non-ASCII, etc),
|
|
|
|
and numbers (positive, negative, zero, NaN, infinity).
|
|
|
|
|
2019-03-31 18:45:58 +00:00
|
|
|
```crystal
|
|
|
|
# List of integers to test against.
|
|
|
|
def various_integers
|
|
|
|
[-7, -1, 0, 1, 42]
|
|
|
|
end
|
|
|
|
|
|
|
|
# Repeat nested tests for every value in `#various_integers`.
|
|
|
|
sample various_integers do |int|
|
|
|
|
# Example that checks if a fictitious method `#format` converts to strings.
|
|
|
|
it "formats correctly" do
|
|
|
|
expect(format(int)).to eq(int.to_s)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2019-03-28 02:08:06 +00:00
|
|
|
Another context type is `given`.
|
|
|
|
This context drastically reduces the amount of code needed in some scenarios.
|
|
|
|
It can be used where one (or more inputs) changes the output of multiple methods.
|
|
|
|
The `given` context gives a concise syntax for this use case.
|
|
|
|
|
2019-03-31 18:45:58 +00:00
|
|
|
```crystal
|
2019-03-31 18:47:22 +00:00
|
|
|
subject(user) { User.new(age) }
|
2019-03-31 18:45:58 +00:00
|
|
|
|
|
|
|
# Each expression in the `given` block is its own test.
|
|
|
|
given age = 10 do
|
|
|
|
expect(user.can_drive?).to be_false
|
|
|
|
expect(user.can_vote?).to be_false
|
|
|
|
end
|
|
|
|
|
|
|
|
given age = 16 do
|
|
|
|
expect(user.can_drive?).to be_true
|
|
|
|
expect(user.can_vote?).to be_false
|
|
|
|
end
|
|
|
|
|
|
|
|
given age = 18 do
|
|
|
|
expect(user.can_drive?).to be_true
|
|
|
|
expect(user.can_vote?).to be_true
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2019-03-28 02:08:06 +00:00
|
|
|
### Assertions
|
|
|
|
|
|
|
|
Spectator supports two formats for assertions (expectations).
|
|
|
|
The preferred format is the "expect syntax".
|
|
|
|
This takes the form:
|
|
|
|
|
|
|
|
```crystal
|
|
|
|
expect(THIS).to eq(THAT)
|
|
|
|
```
|
|
|
|
|
|
|
|
The other format, "should syntax" is used by Crystal's default Spec.
|
|
|
|
|
|
|
|
```
|
|
|
|
THIS.should eq(THAT)
|
|
|
|
```
|
|
|
|
|
|
|
|
The first format doesn't monkey-patch the `Object` type.
|
|
|
|
And as a bonus, it captures the expression or variable passed to `expect()`.
|
|
|
|
For instance, compare these two tests:
|
|
|
|
|
|
|
|
```crystal
|
|
|
|
foo = "Hello world"
|
|
|
|
foo.size.should eq(12) # Wrong on purpose!
|
|
|
|
```
|
|
|
|
|
|
|
|
Produces this error output:
|
|
|
|
|
|
|
|
```text
|
|
|
|
Failure: 11 does not equal 12
|
|
|
|
|
|
|
|
expected: 11
|
|
|
|
actual: 12
|
|
|
|
```
|
|
|
|
|
|
|
|
Which is reasonable, but where did 11 come from?
|
|
|
|
Alternatively, with the "expect syntax":
|
|
|
|
|
|
|
|
```crystal
|
|
|
|
foo = "Hello world"
|
|
|
|
expect(foo.size).to eq(12) # Wrong on purpose!
|
|
|
|
```
|
|
|
|
|
|
|
|
Produces this error output:
|
|
|
|
|
|
|
|
```text
|
|
|
|
Failure: foo.size does not equal 12
|
|
|
|
|
|
|
|
expected: 12
|
|
|
|
actual: 11
|
|
|
|
```
|
|
|
|
|
|
|
|
This makes it clearer what was being tested and failed.
|
|
|
|
|
|
|
|
### Matchers
|
|
|
|
|
|
|
|
Spectator has a variety of matchers for assertions.
|
|
|
|
These are named in such a way to help tests read as plain English.
|
|
|
|
Matchers can be used on any value or block.
|
|
|
|
|
|
|
|
There are typical matchers for testing equality: `eq` and `ne`.
|
|
|
|
And matchers for comparison: `<`, `<=`, `>`, `>=`, `be_within`.
|
|
|
|
There are matchers for checking contents of collections:
|
|
|
|
`contain`, `have`, `start_with`, `end_with`, `be_empty`, `have_key`, and more.
|
2019-03-31 18:45:58 +00:00
|
|
|
See the [wiki](https://gitlab.com/arctic-fox/spectator/wikis/Matchers) for a full list of matchers.
|
2019-03-28 02:08:06 +00:00
|
|
|
|
|
|
|
### Running
|
|
|
|
|
|
|
|
Spectator supports multiple options for running tests.
|
|
|
|
"Fail fast" aborts on the first test failure.
|
|
|
|
"Fail blank" fails if there are no tests.
|
|
|
|
Tests can be filtered by their location and name.
|
|
|
|
Additionally, tests can be randomized.
|
|
|
|
Spectator can be configured with command-line arguments, a config block in a `spec_helper.cr` file, and `.spectator` config file.
|
|
|
|
|
2019-03-31 18:45:58 +00:00
|
|
|
```crystal
|
|
|
|
Spectator.configure do |config|
|
|
|
|
config.fail_blank # Fail on no tests.
|
|
|
|
config.randomize # Randomize test order.
|
|
|
|
config.profile # Display slowest tests.
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2019-03-28 02:08:06 +00:00
|
|
|
### Output
|
|
|
|
|
|
|
|
Spectator matches Crystal's default Spec output with some minor changes.
|
|
|
|
JUnit and TAP are also supported output formats.
|
|
|
|
There is also a highly detailed JSON output.
|
2018-08-18 21:33:20 +00:00
|
|
|
|
|
|
|
Development
|
|
|
|
-----------
|
|
|
|
|
2019-03-28 02:08:06 +00:00
|
|
|
This shard is still under development and is not recommended for production use (same as Crystal).
|
|
|
|
However, feel free to play around with it and use it for non-critical projects.
|
2018-08-30 21:09:29 +00:00
|
|
|
|
|
|
|
### Feature Progress
|
|
|
|
|
2019-03-28 02:08:06 +00:00
|
|
|
In no particular order, features that have been implemented and are planned.
|
|
|
|
Items not marked as completed may have partial implementations.
|
2018-08-30 21:15:01 +00:00
|
|
|
|
|
|
|
- [ ] DSL
|
|
|
|
- [X] `describe` and `context` blocks
|
2018-09-04 22:59:59 +00:00
|
|
|
- [X] Contextual values with `let`, `let!`, `subject`, `described_class`
|
2019-01-02 19:45:46 +00:00
|
|
|
- [X] Test multiple and generated values - `sample`, `random_sample`
|
2019-01-09 22:10:57 +00:00
|
|
|
- [X] Concise syntax - `given`
|
2018-09-15 15:59:55 +00:00
|
|
|
- [X] Before and after hooks - `before_each`, `before_all`, `after_each`, `after_all`, `around_each`
|
2019-01-23 22:15:01 +00:00
|
|
|
- [X] Pre- and post-conditions - `pre_condition`, `post_condition`
|
2018-09-22 18:35:44 +00:00
|
|
|
- [ ] Other hooks - `on_success`, `on_failure`, `on_error`
|
2019-01-09 22:10:57 +00:00
|
|
|
- [X] One-liner syntax
|
2018-09-27 22:11:45 +00:00
|
|
|
- [X] Should syntax - `should`, `should_not`
|
2018-09-13 01:16:41 +00:00
|
|
|
- [X] Helper methods and modules
|
2018-09-13 01:11:36 +00:00
|
|
|
- [ ] Aliasing - custom example group types with preset attributes
|
2018-09-15 16:36:20 +00:00
|
|
|
- [X] Pending tests - `pending`
|
2018-09-13 01:11:36 +00:00
|
|
|
- [ ] Shared examples - `behaves_like`, `include_examples`
|
2018-08-30 21:15:01 +00:00
|
|
|
- [ ] Matchers
|
2019-02-01 22:40:43 +00:00
|
|
|
- [X] Equality matchers - `eq`, `ne`, `be ==`, `be !=`
|
2019-02-02 04:37:48 +00:00
|
|
|
- [X] Comparison matchers - `be <`, `be <=`, `be >`, `be >=`, `be_within[.of]`, `be_close`
|
2019-03-29 05:20:32 +00:00
|
|
|
- [X] Type matchers - `be_a`, `respond_to`
|
2019-03-31 16:48:00 +00:00
|
|
|
- [ ] Collection matchers
|
|
|
|
- [X] `contain`
|
|
|
|
- [X] `have`
|
|
|
|
- [X] `contain_exactly`
|
|
|
|
- [ ] `contain_exactly.in_any_order`
|
|
|
|
- [X] `match_array`
|
|
|
|
- [ ] `match_array.in_any_order`
|
|
|
|
- [X] `start_with`
|
|
|
|
- [X] `end_with`
|
|
|
|
- [X] `be_empty`
|
|
|
|
- [X] `have_key`
|
|
|
|
- [X] `have_value`
|
|
|
|
- [ ] `all`
|
|
|
|
- [ ] `all_satisfy`
|
2019-02-01 22:40:43 +00:00
|
|
|
- [X] Truthy matchers - `be`, `be_true`, `be_truthy`, `be_false`, `be_falsey`, `be_nil`
|
2019-03-22 04:50:02 +00:00
|
|
|
- [X] Error matchers - `raise_error`
|
2019-02-01 22:40:43 +00:00
|
|
|
- [ ] Yield matchers - `yield_control[.times]`, `yield_with_args[.times]`, `yield_with_no_args[.times]`, `yield_successive_args`
|
|
|
|
- [ ] Output matchers - `output[.to_stdout|.to_stderr]`
|
2019-03-31 16:48:00 +00:00
|
|
|
- [ ] Misc. matchers
|
|
|
|
- [ ] `exist`
|
|
|
|
- [X] `match`
|
|
|
|
- [ ] `satisfy`
|
|
|
|
- [ ] `change[.by|.from[.to]|.to|.by_at_least|.by_at_most]`
|
|
|
|
- [X] `have_attributes`
|
|
|
|
- [ ] Compound - `and`, `or`
|
2018-08-30 21:15:01 +00:00
|
|
|
- [ ] Runner
|
2019-03-22 06:14:36 +00:00
|
|
|
- [X] Fail fast
|
2018-09-13 01:11:36 +00:00
|
|
|
- [ ] Test filtering - by name, context, and tags
|
2019-03-22 19:48:47 +00:00
|
|
|
- [X] Fail on no tests
|
2019-03-24 01:56:00 +00:00
|
|
|
- [X] Randomize test order
|
2019-03-22 20:18:03 +00:00
|
|
|
- [X] Dry run - for validation and checking formatted output
|
2019-03-22 16:11:30 +00:00
|
|
|
- [X] Config block in `spec_helper.cr`
|
2019-03-22 04:55:58 +00:00
|
|
|
- [X] Config file - `.spectator`
|
2019-03-23 23:01:03 +00:00
|
|
|
- [X] Reporter and formatting
|
2019-03-03 19:00:56 +00:00
|
|
|
- [X] RSpec/Crystal Spec default
|
2019-03-23 03:52:48 +00:00
|
|
|
- [X] JSON
|
2019-03-23 23:01:03 +00:00
|
|
|
- [X] JUnit
|
2019-03-23 06:25:46 +00:00
|
|
|
- [X] TAP
|
2018-08-30 21:09:29 +00:00
|
|
|
|
2019-03-28 02:08:06 +00:00
|
|
|
### How it Works (in a nutshell)
|
2018-08-30 21:09:29 +00:00
|
|
|
|
|
|
|
This shard makes extensive use of the Crystal macro system to build classes and modules.
|
|
|
|
Each `describe` and `context` block creates a new module nested in its parent.
|
2019-03-28 02:08:06 +00:00
|
|
|
The `it` block creates an example class.
|
2018-08-30 21:09:29 +00:00
|
|
|
An instance of the example class is created to run the test.
|
|
|
|
Each example class includes a context module, which contains all test values and hooks.
|
2018-08-18 21:33:20 +00:00
|
|
|
|
|
|
|
Contributing
|
|
|
|
------------
|
|
|
|
|
|
|
|
1. Fork it (<https://gitlab.com/arctic-fox/spectator/fork/new>)
|
|
|
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
|
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
|
|
4. Push to the branch (`git push origin my-new-feature`)
|
|
|
|
5. Create a new Merge Request
|
|
|
|
|
2019-03-28 02:08:06 +00:00
|
|
|
Please make sure to run `crystal tool format` before submitting.
|
|
|
|
The CI build checks for properly formatted code.
|
|
|
|
|
|
|
|
Tests must be written for any new functionality.
|
|
|
|
Macros that create types are not as easy to test,
|
|
|
|
so they are exempt for the current time.
|
|
|
|
However, please test all code locally with an example spec file.
|
|
|
|
|
2019-04-03 04:10:52 +00:00
|
|
|
Documentation is automatically generated and published to GitLab pages.
|
|
|
|
It can be found here: https://arctic-fox.gitlab.io/spectator
|
|
|
|
|
2018-08-18 21:33:20 +00:00
|
|
|
Contributors
|
|
|
|
------------
|
|
|
|
|
|
|
|
- [arctic-fox](https://gitlab.com/arctic-fox) Michael Miller - creator, maintainer
|