Skip to content

Commit f543f9f

Browse files
regexidentmackwic
authored andcommitted
Implement test environments and before/after closures (#30)
* Add support for test context environments * Move environment into Runner (changing strategy from BFS to DFS in PSPACE) * Add support for `before` and `after` (and variants) closures * Clean up project structure * Make the whole thing parallel in execution. * Make runner work with multiple suites, remove unnecessary lifetimes, etc. * Rustfmt the files + move runner::Configuration in its own module * move Context modules in a directory * cargo fmt * Extract private method and rename inner variables * remove useless `_with` methods in Runner * Remove need for `rayon::initialize` * Remove unused/commented code from example * Demote suite/context/example names from `String` to `&’static str` * Lots of cleanup, a parallel formatter, and lots more * Add convenience macro and example * Improved README * Fix `.or_exit()`, making it less error prone while we’re at it * Refactor Runner simplifying `fn visit()` for `Context<T>` * Improve module exports & documentation * Improve `EventHandler` (now called `RunnerObserver`) * Improve documentation for `ExampleReport` * Clean up suite/context/example header structs & add tests * Add default impl for `RunnerObserver` trait * Move root environment into Suite * cargo fmt * Add minimal tests for suite * Fix indentation of Context tests * Improve imports * Clean up `RunnerObserver` * Change `RunnerObserver` default impls to stable syntax * impl default for configuration runner * first tests of the runner * str is simpler than String * add Runner::wrap_each tests * add Runner tests for wrap_all and Drop trait * Add first test for impl_visitor_block_for_runner * Fix macro, allowing passing of expression, not just identifiers * Fix missing new-line in formatter * Simplify ‘simple’ example * Replace `rspec_run!` macro with function * Some more cleanup * Fix test runner::impl_visitor_block_for_runner::it_can_be_called * Add tests for impl_visitor_example_for_runner * Rename ‘Formatter’ to ‘Logger’ * Improve logger, merging serial & parallel logger
1 parent c13cf67 commit f543f9f

34 files changed

+3174
-1808
lines changed

.gitignore

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,52 @@
1-
# Compiled files
2-
*.o
3-
*.so
4-
*.rlib
5-
*.dll
61

7-
# Executables
8-
*.exe
2+
# Created by https://www.gitignore.io/api/rust
93

4+
### Rust ###
105
# Generated by Cargo
6+
# will have compiled files and executables
117
/target/
128

139
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
1410
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
1511
Cargo.lock
16-
target
17-
Cargo.lock
18-
*.bk
12+
13+
# These are backup files generated by rustfmt
14+
**/*.rs.bk
15+
16+
# These are temporary files generated by racer
17+
*.racertmp
18+
19+
# End of https://www.gitignore.io/api/rust
20+
21+
# Created by https://www.gitignore.io/api/osx
22+
23+
### OSX ###
24+
*.DS_Store
25+
.AppleDouble
26+
.LSOverride
27+
28+
# Icon must end with two \r
29+
Icon
30+
31+
# Thumbnails
32+
._*
33+
34+
# Files that might appear in the root of a volume
35+
.DocumentRevisions-V100
36+
.fseventsd
37+
.Spotlight-V100
38+
.TemporaryItems
39+
.Trashes
40+
.VolumeIcon.icns
41+
.com.apple.timemachine.donotpresent
42+
43+
# Directories potentially created on remote AFP share
44+
.AppleDB
45+
.AppleDesktop
46+
Network Trash Folder
47+
Temporary Items
48+
.apdisk
49+
50+
# End of https://www.gitignore.io/api/osx
51+
.idea/*
52+
*.iml

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ rust:
1111
- nightly
1212
- beta
1313
- stable
14-
- 1.9.0
14+
- 1.19.0
1515
matrix:
1616
allow_failures:
1717
- rust: nightly

Cargo.toml

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
11
[package]
2-
name = "rspec"
2+
authors = [
3+
"Thomas Wickham <[email protected]>",
4+
"Vincent Esche <[email protected]>",
5+
]
36
description = "Write Rspec-like tests with stable rust"
4-
version = "1.0.0-beta.3"
5-
authors = ["Thomas Wickham <[email protected]>"]
6-
license = "MPL-2.0"
77
homepage = "https://mackwic.github.io/rspec"
8-
repository = "https://github.com/mackwic/rspec"
8+
keywords = [
9+
"rspec",
10+
"test",
11+
"harness",
12+
"tdd",
13+
"bdd",
14+
]
15+
license = "MPL-2.0"
16+
name = "rspec"
917
readme = "README.md"
10-
keywords = ["rspec", "test", "harness", "bdd"]
18+
repository = "https://github.com/mackwic/rspec"
19+
version = "1.0.0-beta.4"
20+
21+
[build-dependencies.clippy]
22+
optional = true
23+
version = "0.0.153"
1124

1225
[dependencies]
13-
clippy = {version = "0.0", optional = true}
26+
colored = "1.4.0"
27+
derive-new = "0.5.0"
28+
derive_builder = "0.5.0"
29+
rayon = "0.8.2"
30+
31+
[dependencies.expectest]
32+
optional = true
33+
version = "0.9.1"
1434

1535
[features]
1636
default = []
37+
expectest_compat = ["expectest"]

README.md

Lines changed: 95 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The last stable documentation is available for consultation at
1212
[mackwic.github.io/rspec](https://mackwic.github.io/rspec).
1313

1414
**All rspec releases are garanteed to compile against the latest stable rust and
15-
are tested on all rust versions from the 1.9**.
15+
are tested on all rust versions from the 1.19**.
1616

1717
## How to use
1818

@@ -32,90 +32,130 @@ extern crate rspec;
3232

3333
You can see complete examples in the [`examples/`](https://github.com/mackwic/rspec/tree/master/examples) directory.
3434

35-
You can now use rspec in your unit tests, this example will use the `cargo test`
36-
runner:
3735

3836
```rust
39-
fn add(x: u32, y: u32) -> u64 {
40-
x + y
41-
}
37+
extern crate rspec;
4238

43-
#[test]
44-
fn test_add() {
45-
rdescribe("add", |ctx| {
46-
ctx.describe("0 <= x + y <= u32::MAX", |ctx| {
47-
ctx.it("2 + 4 = 6", || {
48-
assert_eq!(6, add(2, 4))
39+
pub fn main() {
40+
// Use a local struct to provide the test contexts with an environment.
41+
// The environment will contain the subject that is to be tested
42+
// along with any additional data you might need during the test run:
43+
#[derive(Clone, Default, Debug)]
44+
struct Environment {
45+
// ...
46+
}
47+
48+
// `rspec::run(…)` is a convenience wrapper that takes care of setting up
49+
// a runner, logger, configuration and running the test suite for you.
50+
// If you want more direct control, you can manually set up those things, too.
51+
rspec::run(&rspec::describe("rspec, a BDD testing framework", Environment::default(), |ctx| {
52+
// `describe`, or any of its equivalents, opens the root context
53+
// of your test suite. Within you can then either define test examples:
54+
ctx.it("can define top-level tests", |_| true);
55+
56+
// or make use of sub-contexts to add some structure to your test suite:
57+
ctx.specify("contexts give your tests structure and reduce redundancy", |ctx| {
58+
59+
ctx.before(|_| {
60+
// Executed once, before any of the contexts/examples is entered.
4961
});
5062

51-
ctx.it("4 + 4 = 8", || {
52-
assert_eq!(8, add(4, 4))
63+
ctx.after(|_| {
64+
// Executed once, after all of the contexts/examples have been exited.
65+
});
66+
67+
ctx.specify("rspec can handle results", |ctx| {
68+
ctx.it("passes if the return is_ok()", |_| Ok(()) as Result<(),()>);
69+
ctx.it("failes if the return is_err()", |_| Err(()) as Result<(),()>);
70+
});
71+
72+
ctx.describe("rspec can handle bools", |ctx| {
73+
ctx.it("should pass if true", |_| true);
74+
ctx.it("should fail if false", |_| false);
75+
ctx.it("is convenient for comparisons", |_| (42 % 37 + 2) > 3);
76+
});
77+
78+
ctx.describe("rspec can handle units", |ctx| {
79+
ctx.it("should pass if the return is ()", |_| {});
80+
});
81+
82+
ctx.describe("rspec can handle panics", |ctx| {
83+
ctx.it("is convenient for asserts", |_| assert_eq!(1, 1));
5384
});
54-
});
5585

56-
ctx.it("is associative", || {
57-
assert_eq!(add(2, 1), add(1, 2));
58-
assert_eq!(add(4, 1), add(1, 4));
59-
assert_eq!(add(4, 5), add(5, 4));
60-
assert_eq!(add(12, 1), add(1, 12));
6186
});
62-
});
87+
})); // exits the process with a failure code if one of the tests failed.
6388
}
6489
```
6590

66-
You can also use rspec in integration tests, this example uses the rspec runner:
91+
### Suites, Contexts & Examples
6792

68-
```rust
69-
extern crate rspec;
70-
use rspec::context::describe;
71-
use std::io;
93+
rspec provides three variants for each of the structural elements:
7294

73-
pub fn main() {
74-
let stdout = &mut io::stdout();
75-
let mut formatter = rspec::formatter::Simple::new(stdout);
76-
let mut runner = describe("rspec is a classic BDD testing", |ctx| {
95+
| | Variant A | Variant B | Variant C |
96+
|-----------|------------|------------|------------|
97+
| Suites: | `suite` | `describe` | `given` |
98+
| Contexts: | `context` | `specify` | `when` |
99+
| Examples: | `example` | `it` | `then` |
100+
101+
**Note:** While the intended use is to stick to a single variant per test suite
102+
it is possible to freely mix structural elements across variants.
77103

78-
ctx.it("can define tests", || true);
104+
#### Variant A: `suite`, `context` & `example`
79105

80-
ctx.describe("rspec use results for tests results", |ctx| {
106+
```rust
107+
runner.run(&rspec::suite("opens a suite", /* environment */, |ctx| {
108+
ctx.context("opens a context", |ctx| {
109+
ctx.example("opens an example", |env| /* test condition */ );
110+
});
111+
}));
112+
```
81113

82-
ctx.it("passed if the return is_ok()", || Ok(()) as Result<(),()>);
114+
#### Variant B: `describe`, `specify` & `it`
83115

84-
ctx.it("failed if the return is_err()", || Err(()) as Result<(),()>);
85-
});
116+
```rust
117+
runner.run(&rspec::describe("opens a suite", /* environment */, |ctx| {
118+
ctx.specify("opens a context", |ctx| {
119+
ctx.it("opens an example", |env| /* test condition */ );
120+
});
121+
}));
122+
```
86123

87-
ctx.describe("rspec can use bools", |ctx| {
124+
#### Variant C: `given`, `when` & `then`
88125

89-
ctx.it("should pass if true", || true);
126+
```rust
127+
runner.run(&rspec::given("opens a suite", /* environment */, |ctx| {
128+
ctx.when("opens a context", |ctx| {
129+
ctx.then("opens an example", |env| /* test condition */ );
130+
});
131+
}));
132+
```
90133

91-
ctx.it("should fail if false", || false);
134+
### Before & After
92135

93-
ctx.it("is convenient for comparisons", || {
94-
(42 % 37 + 2) > 3
95-
})
96-
});
136+
| | All | Each |
137+
|---------|-----------------------|---------------|
138+
| Before: | `before`/`before_all` | `before_each` |
139+
| After: | `after` /`after_all` | `after_each` |
97140

98-
ctx.describe("rspec can use units", |ctx| {
141+
#### All
99142

100-
ctx.it("should pass if the return is ()", || {});
143+
The "All" variants of before and after blocks are executed once upon
144+
entering (or exiting, respectively) the given context.
101145

102-
ctx.it("is convenient for asserts", || assert_eq!(1, 1));
103-
});
104-
});
105-
runner.add_event_handler(&mut formatter);
106-
runner.run().unwrap();
107-
}
146+
#### Each
108147

109-
```
148+
`before_each` and `after_each` blocks are executed once before each of the
149+
given context's sub-contexts or examples.
110150

111-
*Note:*
112-
- `describe` has 4 aliases: `specify`, `context`, `given`, and `when`
113-
- `it` has 2 aliases: `example`, and `then`
151+
### More Examples
114152

115153
Again, you can see complete examples in the [`examples/`](https://github.com/mackwic/rspec/tree/master/examples) directory.
116154

155+
## Documentation
156+
117157
The last stable documentation is available for consultation at
118-
[mackwic.github.io/rspec](https://mackwic.github.io/rspec).
158+
[https://docs.rs/rspec](https://docs.rs/rspec).
119159

120160
## Contributions
121161

examples/advanced.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
extern crate rspec;
2+
3+
use std::collections::BTreeSet;
4+
5+
pub fn main() {
6+
#[derive(Clone, Debug)]
7+
struct Environment {
8+
set: BTreeSet<usize>,
9+
len_before: usize,
10+
}
11+
12+
let environment = Environment {
13+
set: BTreeSet::new(),
14+
len_before: 0,
15+
};
16+
17+
rspec::run(&rspec::given("a BTreeSet", environment, |ctx| {
18+
ctx.when("not having added any items", |ctx| {
19+
ctx.then("it is empty", |env| assert!(env.set.is_empty()));
20+
});
21+
22+
ctx.when("adding an new item", |ctx| {
23+
ctx.before_all(|env| {
24+
env.len_before = env.set.len();
25+
env.set.insert(42);
26+
});
27+
28+
ctx.then("it is not empty any more", |env| {
29+
assert!(!env.set.is_empty());
30+
});
31+
32+
ctx.then("its len increases by 1", move |env| {
33+
assert_eq!(env.set.len(), env.len_before + 1);
34+
});
35+
36+
ctx.when("adding it again", |ctx| {
37+
ctx.before_all(|env| {
38+
env.len_before = env.set.len();
39+
env.set.insert(42);
40+
});
41+
42+
ctx.then("its len remains the same", move |env| {
43+
assert_eq!(env.set.len(), env.len_before);
44+
});
45+
});
46+
});
47+
48+
ctx.when("returning to outer context", |ctx| {
49+
ctx.then("it is still empty", |env| assert!(env.set.is_empty()));
50+
});
51+
52+
ctx.then("panic!(…) fails", move |_env| {
53+
panic!("Some reason for failure.")
54+
});
55+
}));
56+
}

examples/given_when_then.rs

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)