summaryrefslogtreecommitdiff
path: root/saga.yarn
blob: 0a315985c57b8faf1876c146a94c80f88a786035 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
---
title: Acceptance testing using Saga
author:
- Lars Wirzenius
- Daniel Silverstone
date: %VERSION%
keywords:
- automated acceptance testing
- scenario testing
- gherkin
- cucumber
abstract: |
  Saga is a tool that supports acceptance testing in two ways: it is a
  way to implement and run automated acceptance tests, and also
  presents the acceptance test suite to non-expert readers as a
  human-readable text document.

  This document explains Saga, its architecture, and its input
  language.
...

# Introduction

Saga is a tool for acceptance testing of software. This means it helps
software developers and teams to make sure their software fulfils the
acceptance criteria for the software. Such criteria may come from
users, the developers, their employers, or other elsewhere.

Saga is specifically meant for automated acceptance testing. It takes a
two-pronged approach, where it lets developers write automated
tests for all the acceptance criteria they have, and runs the tests.
On the other hand, Saga also produces a PDF file, which documents the
automated tests for non-technical stakeholders.

More concretely, Saga helps developers implement and document their
automated acceptance tests in a way that, at the same time, helps the
developers automatically test their software, and write documentation
for the tests in way that doesn't require programming knowledge to
understand.

Saga is meant to be a tool for developers, who use it to produce a
document, which is meant to facilitate communication between various
shareholders of the software being developed.

Saga's overall working principle is that the tests are implemented and
documented in a number of source files, which Saga reads to execute
tests, and to produce a PDF document for non-developer consumption.

# Saga architecture

Saga's architecture it to read input files, and produce two outputs.
On the one hand, it outputs a program that executes the tests
specified in the input files. On the other hand it outputs a
human-readable document (as PDF), for communicating what is being
tested.

The approach is that of a compiler. Saga doesn't act as an
interpreter, which executes each test as it runs, and instead produces
a program (or the source code of a program) which can execute all the
tests. Instead, Saga produces a complete program, which performs all
the tests, when it's run.

Saga will be able to produce the test program in various languages,
probably using some form of templating that is partially under user
control. This way, Saga can support, say, Python and Rust, and it's
the user's choice which language they're most comfortable with.

Acceptance tests are expressed to Saga in the form of test scenarios,
in which a sequence of actions are taken, and then the results are
checked. If the checks fail, the scenario fails.

Saga runs the scenarios concurrently (but see the USING keyword),
within the constrains of hardware resources. If Saga determines it
doesn't have all the CPU and RAM to run all scenarios at once, it will
run fewer, but randomly chosen scenarios concurrently, to more likely
to detect unintentional dependencies between scenarios.

# The Saga input language

Saga input consists of three types of files:

* markdown file which document that acceptance tests; these are
  independent of the step implementation language
* binding files which bind specific scenario steps to their
  implementations; these are also independent of the implementation
  language
* scenario step implementations, which are implemented in a specific
  programming language (e.g., Python or Rust)

The input files for a simple acceptance test suite for Saga would be
divided into three files: `foo.mdwn`, `foo.yaml`, and `foo.py`
(assuming implementation in Python).

## Markdown files

[fenced code blocks]: https://pandoc.org/MANUAL.html#fenced-code-blocks
[Gherkin]: https://en.wikipedia.org/wiki/Cucumber_(software)#Gherkin_language
[Cucumber]: https://en.wikipedia.org/wiki/Cucumber_(software)

The Saga input language is markdown files using [fenced code blocks][]
with backticks. The code blocks MUST indicate that they contain Saga
language:

    ```saga
    given a service
    and I am Tomjon
    when I access the service
    then it's OK
    ```

Any other code blocks are ignored by Saga.

Saga understands the full Markdown language, mostly by ignoring
everything except its own code blocks and headings. It uses [Pandoc][]
to produce PDF files, and anything that Pandoc supports is OK to use.

[Pandoc]: https://pandoc.org/

Saga can treat multiple Markdown files as one, as if they had been
concatenated with the **cat**(1) utility. Within the logical file,
normal Markdown and Pandoc markup can be used to structure the
document in any way that aids human understanding of the acceptance
test suite, which the caveat that chapter or section headings are used
by Saga to group code blocks into scenarios.

All code blocks for the same scenario MUST be grouped under a single
heading. Sub-headings are permitted within a scenario, but the next
heading at the same or a higher level will end the scenario. This
allows for scenarios to begin at any level, but not to leak into a
wider scope within the acceptance document. For example, a scenario
which starts after a level 2 heading may have subdivisions marked with
level 3 or below headings, but will end at the next level 2 or level 1
heading.

FIXME: Discuss whether it would be useful for Saga to support, say,
PlantUML and Graphviz code blocks for graphs and stuff.

Within the Saga code blocks, Saga understands a special language,
derived from [Gherkin][], as defined by the [Cucumber][] testing tool.
The language understood by Saga has the following general structure:

* each logical line starts with a keyword at the beginning of the line
* logical lines may be broken into physical lines, by starting the
  continuation lines with one or more space or TAB characters; the
  physical line break and white space characters are preserved
* logical lines define steps in a test scenario
* the meaning and implementation of the steps are defined by other
  Saga input files
* the keywords are: ASSUMING, USING, GIVEN, WHEN, THEN, AND, with
  meanings defined below; keywords can be written in upper or lower
  case, or mixes, Saga doesn't care

The keywords have the following meanings:

* ASSUMING—a condition for the scenario; all ASSUMING steps MUST
  be at the beginning of the scenario, and may not be preceded by
  other steps; if the condition fails, the scenario is skipped.

  This is meant to be used for things like skipping test scenarios
  that require specific software to be installed in the test
  environment, or access to external services, but which can't be
  required for all runs of the acceptance tests.

  All ASSUMING steps MUST come first in the scenario.

* USING—indicate that the scenario uses a resource such as a
  database, that's constrained and can't be used by all scenarios if
  they run concurrently. When scenarios declare the resource, Saga can
  limit which scenarios run concurrently.

  For example, if several test require uncontested use of the GPU, of
  which there is typically only one per machine, they can all declare
  "using the graphical processing unit", and Saga will run them one at
  a time.

  (This is an intentionally simplistic way of controlling concurrency.
  The goal is to be simple and correct rather then get maximal
  concurrency.)

  The actual management of resources belongs to the implementation
  language specific parts of the generated test program, at runtime,
  not the Saga compiler.

  All USING steps MUST come at the beginning of the scenario, except
  the ASSUMING steps may precede them.

* GIVEN—set up the test environment for the action (WHEN). This
  might create files, start a background process, or something like
  that. This also sets up the reversal of the setup, so that any
  background processes are stopped automatically after the scenario
  has ended. The setup and cleanup MUST succeed, or the scenario will
  fail.

* WHEN—perform the action that is being tested. This MUST
  succeed. This might, for example, execute a command line program,
  and capture its output and exit code.

* THEN—test the results of the action. This would examine the
  output and exit code of the program run in a WHEN step, or examine
  current content of the database, or whatever is needed.

* AND—this keyword exists to make scenarios "read" better in
  English. The keyword indicates that this step should use the same
  keyword as the previous step, whatever that keyword is. For example,
  a step "THEN output is empty" might be followed by "AND the exit
  code is 0" rather than "THEN the exit code is 0".

## Bindings

FIXME: The binding specification needs thought. This is just a sketch.

Binding files match scenario steps to functions that implement them,
using regular expressions. The bindings may also extract parts of the
steps, and pass them onto the functions as parameters.

Binding files are YAML files, with lists of bindings, each binding
being a dict. For example:

```yaml
- given: a service
  function: start_service

- given: I am (name:\S+)
  match:
    name: 
      type: string
      save: true

- when: I access the service
  function: access_service
  require:
    name: string
  produces:
    exit_code: int

- then: it's OK
  function: check_access_was_ok
  require:
    exit_code: int
```

In the example above, the "I am" step extracts the name of the user
from the step. It's type is declared, and the value is saved for use
by a later step.

The "I access" step expects the name to have been set by a previous
step. Saga will check that the name is set, and give an error if it
isn't, before any scenario runs. If name is set, it is given to the
function to be called as a function argument.

The "I access" step further sets the variable "exit_code", and the
"it's OK" step expects it to be set.

## Step implementations

Continuing the example from the previous section, the following Python
code might implement the functions:

```python
def start_service():
    ...

def access_service(name):
    ...
    return {
        'exit_code': 0,
    }

def check_access_was_ok(exit_code=None):
    assert exit_code == 0
```

Saga will produce a Python program, which calls these functions in
order, and passes values between them via function arguments and
return values. The program will handle running scenarios concurrently,
and taking care of USING constraints, and other resource constraints.

# Acceptance tests for Saga

FIXME: This needs to be written eventually, when Saga is implemented.