Skip to content

Commit 27449e2

Browse files
committed
Add documentation
1 parent 46c22fd commit 27449e2

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,10 @@ Any contribution is welcome:
4242
- developing a new feature
4343

4444
Please use this Github project for contributing, either through an issue or a Pull Request.
45+
46+
### Documentation
47+
48+
These pages aim to help Cucumber Scala developers understand the codebase.
49+
50+
- [Scala implementation details](docs/scala_implementation.md)
51+
- [Project structure](docs/project_structure.md)

docs/project_structure.md

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Project structure
2+
3+
The Cucumber Scala project is a Maven multimodule project:
4+
- `scala` module: contains the codebase of the Cucumber Scala implementation
5+
- `scala_2.11` submodule: build for Scala 2.11.x
6+
- `scala_2.12` submodule: build for Scala 2.12.x
7+
- `scala_2.13` submodule: build for Scala 2.13.x
8+
- `examples` module: contains a sample project
9+
10+
## Cross compilation
11+
12+
The cross compilation for the different Scala versions is handled with 3 different Maven projects: the submodules of the `scala` module.
13+
14+
Each project has a different Scala version as dependency:
15+
```xml
16+
<dependency>
17+
<groupId>org.scala-lang</groupId>
18+
<artifactId>scala-compiler</artifactId>
19+
<version>${scala.2.13.version}</version>
20+
<scope>provided</scope>
21+
</dependency>
22+
```
23+
24+
To not copy/paste the sources across the 3 projects, the sources are put in a separated folder called `sources` in the `scala` module.
25+
Each project uses it by defining the following properties:
26+
```xml
27+
<sourceDirectory>../sources/src/main/scala</sourceDirectory>
28+
<resources>
29+
<resource>
30+
<directory>../sources/src/main/resources</directory>
31+
</resource>
32+
</resources>
33+
<testSourceDirectory>../sources/src/test/scala</testSourceDirectory>
34+
<testResources>
35+
<testResource>
36+
<directory>../sources/src/test/resources</directory>
37+
</testResource>
38+
</testResources>
39+
```
40+
41+
**Note:** when using your favorite IDE, you might have to "close" or "unload" 2 of the 3 projects.
42+
Some IDE are not able to handle shared sources because a source path can be attached to a single IDE project.
43+
If so, only loading the latest (`scala_2.13` project) is recommended.
44+
45+
## Language traits generation
46+
47+
The language traits (`io.cucumber.scala.EN` for instance) are generated automatically using
48+
a Groovy script at compile time.
49+
50+
See in `sources/src/main/groovy/` folder.

docs/scala_implementation.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Scala implementation details
2+
3+
This page covers some details about the Cucumber Scala implementation.
4+
5+
## Running a Cucumber test
6+
7+
### Backend
8+
9+
From Cucumber core perspective, the entrypoint of a Cucumber implementation is what is called "backend".
10+
11+
The `BackendServiceLoader` core service looks for a `BackendProviderService` implementation.
12+
Ours is defined in the class `ScalaBackendProviderService`.
13+
14+
The implementing class also has to be registered as a "Java Service" in the `META-INF/services/io.cucumber.core.backend.BackendProviderService` file (in the `resources` folder).
15+
16+
### Loading the glue
17+
18+
When a Cucumber test starts, a Cucumber Runner starts and a `ScalaBackend` instance is created.
19+
The `ScalaBackend` instance will be used for running all the scenarios which are part of the test (defined by the _features path_ and the _glue path_).
20+
21+
The first thing the Runner does is to "load the glue", that is find all the hooks and step definitions and register them.
22+
This is handled by the `ScalaBackend#loadGlue()` method.
23+
24+
#### Scala implementation
25+
26+
In the Cucumber Scala implementation, loading the glue code means:
27+
- finding all the **classes** inheriting `io.cucumber.scala.ScalaDsl` in the _glue path_, and for each:
28+
- add it to the `Container` instance provided by Cucumber Core
29+
- finding all the **objects** singletons instances inheriting `io.cucumber.scala.ScalaDsl` in the _glue path_ and for each:
30+
- extract the hooks and step definitions from it
31+
- add the definitions to the `Glue` instance provided by Cucumber Core, as NOT `ScenarioScoped`
32+
33+
Ideally all the glue code should be instantiated further (see next section), this is why we register classes (actually a list of `Class`) to the Container.
34+
But this cannot work for objects because they are by definitions singletons and already instantiated way before Cucumber.
35+
Thus, objects are not registered in the Container and their lifecycle is out of Cucumber scope.
36+
37+
### Running a scenario
38+
39+
For each scenario, the `buildWorld()` method of the backend is called.
40+
This is where the glue code should be initialized.
41+
42+
#### Scala implementation
43+
44+
For each **class** identified when loading the glue:
45+
- an instance is created by the `Lookup` provided by Cucumber Core
46+
- hooks and steps definitions are extracted from it
47+
- definitions are added to the `Glue` instance provided by Cucumber Core, as `ScenarioScoped`
48+
49+
Being `ScenarioScoped` ensure instances are flushed at the end of the scenario and recreated for the next one.
50+
51+
## Scala DSL
52+
53+
The Scala DSL is made in a way that any class instance or object extending it contains what we call a **registry**:
54+
a list of the hooks and step definitions it contains.
55+
This is the purpose of `ScalaDslRegistry`.
56+
57+
The registry is populated when the class instance or the object is created.
58+
Unlike other implementations there is no need to use annotations or reflection here.
59+
This is actually **similar to the Java8/Lambda implementation**.

0 commit comments

Comments
 (0)