Skip to content

Commit

Permalink
Merge pull request #51 from ashleyfrieze/af-bdd-1
Browse files Browse the repository at this point in the history
Adding Gherkin-like feature set, see #50
  • Loading branch information
greghaskins authored Nov 28, 2016
2 parents bd63595 + 629c7fb commit 0064181
Show file tree
Hide file tree
Showing 15 changed files with 610 additions and 51 deletions.
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.java text eol=lf
*.md text eol=lf
*.gradle text eol=lf
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,35 @@ public class ExampleSpecs {

});


describe("The Variable convenience wrapper", () -> {

final Variable<Integer> counter = new Variable<>();

beforeEach(() -> {
counter.set(0);
});

beforeEach(() -> {
counter.set(counter.get()+1);
});

it("lets you work around Java's requirement that closures only use `final` variables", () -> {
counter.set(counter.get()+1);
assertThat(counter.get(), is(2));
});

it("can optionally have an initial value set", () -> {
final Variable<String> name = new Variable<>("Alice");
assertThat(name.get(), is("Alice"));
});

it("has a null value if not specified", () -> {
final Variable<String> name = new Variable<>();
assertNull(name.get());
});

});
}
}
```
Expand Down Expand Up @@ -209,6 +238,21 @@ describe("Ignored specs", () -> {
});
});
```
### Gherkin Syntax
The following Gherkin-like constructs are available (within the `GherkinSyntax` class):

* Feature - this is a suite, declared using `feature`
* Scenario - this is also a suite, declared with `scenario` which lives inside a feature
* Scenarios can live inside other scenarios, though that's not encouraged
* All previous steps in a scenario must have passed for the next to run - the scenario is aborted when a step fails
* ScenarioOutline - this is a templated scenario, declared with `scenarioOutline` allowing you to parameterise a scenario
* You provide a stream of values, each of which is consumed by the definition of your scenario
* n-dimensional test sets might be achieved by nested Scenario Outlines
* Given/When/Then/And - these are all just steps - the same level as `it` specs. They are declared with:
* `given`
* `when`
* `then`
* `and`

### Common Variable Initialization

Expand Down Expand Up @@ -239,6 +283,12 @@ describe("The `let` helper function", () -> {
});
```

The `Variable` class is a simpler construct than `let`, intended to help you work around Java's requirement that closures only use final variables. The `Variable` object boxes a value, enabling it to be accessed by all nearby specs.

It is generally poor practice to have co-dependent tests, but some suites may have this requirement - example, a final `afterAll` or `it` to check a summary of the other specs.

The Gherkin syntax breaks tests down into independent steps, which MUST share state in order to function as a single scenario, and the use of `Variable` objects is a transparent way to do that - especially since it avoids dropping values into the fields of the parent class.

## Supported Features

The Spectrum API is designed to be familiar to Jasmine and RSpec users, while remaining compatible with JUnit. The features and behavior of those libraries help guide decisions on how Specturm should work, both for common scenarios and edge cases. (See [the discussion on #41](https://github.com/greghaskins/spectrum/pull/41#issuecomment-238729178) for an example of how this factors into design decisions.)
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/com/greghaskins/spectrum/Block.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.greghaskins.spectrum;

/**
* A generic code block with a {@link #run()} method to perform any action. Usually defined by a
* lambda function.
*/
@FunctionalInterface
interface Block {
public interface Block {
void run() throws Throwable;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.greghaskins.spectrum;

import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;

/**
* A listener to detect test failure.
*/
class FailureDetectingRunListener extends RunListener {
private boolean hasFailedYet = false;

/**
* Has the run failed since we've been listening.
* @return whether any previous failures have been reported
*/
boolean hasFailedYet() {
return hasFailedYet;
}

@Override
public void testFailure(Failure failure) throws Exception {
super.testFailure(failure);
hasFailedYet = true;
}

@Override
public void testAssumptionFailure(Failure failure) {
super.testAssumptionFailure(failure);
hasFailedYet = true;
}
}
68 changes: 68 additions & 0 deletions src/main/java/com/greghaskins/spectrum/GherkinSyntax.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.greghaskins.spectrum;

import static com.greghaskins.spectrum.Spectrum.compositeSpec;
import static com.greghaskins.spectrum.Spectrum.describe;
import static com.greghaskins.spectrum.Spectrum.it;

/**
* A translation from Spectrum describe/it to Gherkin like Feature/Scenario/Given/When/Then syntax
* Note - the beforeEach and afterEach will still be executed between given/when/then steps which
* may not make sense in many situations.
*/
public class GherkinSyntax {
/**
* Describes a feature of the system. A feature may have many scenarios.
* @param featureName name of feature
* @param block the contents of the feature
*/
public static void feature(final String featureName, final Block block) {
describe("Feature: " + featureName, block);
}

/**
* Describes a scenario of the system. These can be at root level, though are best grouped
* inside features.
* @param scenarioName name of scenario
* @param block the contents of the scenario - given/when/then steps
*/
public static void scenario(final String scenarioName, final Block block) {
compositeSpec("Scenario: " + scenarioName, block);
}

/**
* A gherkin like given block.
* @param behavior the behaviour to associate with the precondition
* @param block how to execute that precondition
*/
public static void given(final String behavior, final Block block) {
it("Given " + behavior, block);
}

/**
* A gherkin like when block.
* @param behavior the behaviour to associate with the manipulation of the system under test
* @param block how to execute that behaviour
*/
public static void when(final String behavior, final Block block) {
it("When " + behavior, block);
}

/**
* A gherkin like then block.
* @param behavior the behaviour to associate with the postcondition
* @param block how to execute that postcondition
*/
public static void then(final String behavior, final Block block) {
it("Then " + behavior, block);
}

/**
* An and block.
* @param behavior what we would like to describe as an and
* @param block how to achieve the and block
*/
public static void and(final String behavior, final Block block) {
it("And " + behavior, block);
}

}
3 changes: 3 additions & 0 deletions src/main/java/com/greghaskins/spectrum/NotifyingBlock.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.greghaskins.spectrum;

import org.junit.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
Expand All @@ -13,6 +14,8 @@ static NotifyingBlock wrap(final Block block) {
return (description, notifier) -> {
try {
block.run();
} catch (final AssumptionViolatedException assumptionViolation) {
notifier.fireTestAssumptionFailed(new Failure(description, assumptionViolation));
} catch (final Throwable exception) {
notifier.fireTestFailure(new Failure(description, exception));
}
Expand Down
Loading

0 comments on commit 0064181

Please sign in to comment.