Skip to content

Commit 623b040

Browse files
authored
update testing doc (#1422)
* remove mentions to hidden `std testing` module * minor fixes - `use std assert` at the start - consistently call `assert ...` instead of `std assert ...` - split "command" and output blocks - merge `span` and `end` into `span` for the last example * write a section to run tests * fix spelling
1 parent 934d420 commit 623b040

File tree

1 file changed

+98
-96
lines changed

1 file changed

+98
-96
lines changed

book/testing.md

Lines changed: 98 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,44 @@
11
# Testing your Nushell code
22

3-
To ensure that your code works as expected, you can use the [testing module](https://github.com/nushell/nushell/blob/main/crates/nu-std/testing.nu).
4-
5-
## Quick start
6-
7-
Download the [testing module](https://raw.githubusercontent.com/nushell/nushell/main/crates/nu-std/testing.nu) and save it as `testing.nu` in the folder with your project.
3+
## Assert commands
84

9-
Have a file, called `test_math.nu`:
5+
Nushell provides a set of "assertion" commands in the standard library.
6+
One could use built-in equality / order tests such as `==` or `<=` or more complex commands and throw errors manually when an expected condition fails, but using what the standard library has to offer is arguably easier!
107

11-
```nu
8+
In the following, it will be assumed that the `std assert` module has been imported inside the current scope
9+
```nushell
1210
use std assert
13-
14-
#[test]
15-
def test_addition [] {
16-
assert equal (1 + 2) 3
17-
}
18-
19-
#[test]
20-
#[ignore]
21-
def test_skip [] {
22-
# this won't be run
23-
}
24-
25-
#[test]
26-
def test_failing [] {
27-
assert false "This is just for testing"
28-
}
2911
```
3012

31-
Run the tests:
13+
The foundation for every assertion is the `std assert` command. If the condition is not true, it makes an error.
3214

3315
```nu
34-
❯ use testing.nu run-tests
35-
❯ run-tests
36-
INF|2023-04-12T10:42:29.099|Running tests in test_math
37-
Error:
38-
× This is just for testing
39-
╭─[C:\wip\test_math.nu:13:1]
40-
13 │ def test_failing [] {
41-
14 │ assert false "This is just for testing"
42-
· ──┬──
43-
· ╰── It is not true.
44-
15 │ }
45-
╰────
46-
47-
48-
WRN|2023-04-12T10:42:31.086|Test case test_skip is skipped
49-
Error:
50-
× some tests did not pass (see complete errors above):
51-
52-
│ test_math test_addition
53-
│ ⨯ test_math test_failing
54-
│ s test_math test_skip
55-
16+
assert (1 == 2)
17+
```
5618
```
57-
58-
## Assert commands
59-
60-
The foundation for every assertion is the `std assert` command. If the condition is not true, it makes an error. For example:
61-
62-
```nu
63-
❯ use std assert
64-
❯ std assert (1 == 2)
6519
Error:
6620
× Assertion failed.
6721
╭─[entry #13:1:1]
68-
1 │ std assert (1 == 2)
69-
· ───┬──
70-
· ╰── It is not true.
22+
1 │ assert (1 == 2)
23+
· ───┬──
24+
· ╰── It is not true.
7125
╰────
7226
```
7327

7428
Optionally, a message can be set to show the intention of the assert command, what went wrong or what was expected:
7529

7630
```nu
77-
❯ std assert ($a == 19) $"The lockout code is wrong, received: ($a)"
31+
let a = 0
32+
assert ($a == 19) $"The lockout code is wrong, received: ($a)"
33+
```
34+
```
7835
Error:
7936
× The lockout code is wrong, received: 13
8037
╭─[entry #25:1:1]
81-
1 │ std assert ($a == 19) $"The lockout code is wrong, received: ($a)"
82-
· ────┬───
83-
· ╰── It is not true.
38+
1 │ let a = 0
39+
2 │ assert ($a == 19) $"The lockout code is wrong, received: ($a)"
40+
· ────┬───
41+
· ╰── It is not true.
8442
╰────
8543
```
8644

@@ -89,46 +47,56 @@ There are many assert commands, which behave exactly as the base one with the pr
8947
For example this is not so helpful without additional message:
9048

9149
```nu
92-
❯ std assert ($b | str contains $a)
93-
Error:
94-
× Assertion failed.
95-
╭─[entry #35:1:1]
96-
1 │ assert ($b | str contains $a)
97-
· ──────┬─────
98-
· ╰── It is not true.
50+
let a = "foo"
51+
let b = "bar"
52+
assert ($b | str contains $a)
53+
```
54+
```
55+
Error: × Assertion failed.
56+
╭─[entry #5:3:8]
57+
2 │ let b = "bar"
58+
3 │ assert ($b | str contains $a)
59+
· ───────────┬──────────
60+
· ╰── It is not true.
9961
╰────
10062
```
10163

10264
While with using `assert str contains`:
10365

10466
```nu
105-
❯ std assert str contains $b $a
106-
Error:
107-
× Assertion failed.
108-
╭─[entry #34:1:1]
109-
1 │ assert str contains $b $a
67+
let a = "a needle"
68+
let b = "haystack"
69+
assert str contains $b $a
70+
```
71+
```
72+
Error: × Assertion failed.
73+
╭─[entry #7:3:21]
74+
2 │ let b = "bar"
75+
3 │ assert str contains $b $a
11076
· ──┬──
111-
· ╰── 'haystack' does not contain 'a needle'.
77+
· ╰─┤ This does not contain 'a needle'.
78+
· │ value: "haystack"
11279
╰────
11380
```
11481

11582
In general for base `assert` command it is encouraged to always provide the additional message to show what went wrong. If you cannot use any built-in assert command, you can create a custom one with passing the label for [`error make`](/commands/docs/error_make.md) for the `assert` command:
11683

11784
```nu
11885
def "assert even" [number: int] {
119-
std assert ($number mod 2 == 0) --error-label {
120-
start: (metadata $number).span.start,
121-
end: (metadata $number).span.end,
86+
assert ($number mod 2 == 0) --error-label {
12287
text: $"($number) is not an even number",
88+
span: (metadata $number).span,
12389
}
12490
}
12591
```
12692

12793
Then you'll have your detailed custom error message:
12894

12995
```nu
130-
❯ let $a = 13
131-
❯ assert even $a
96+
let $a = 13
97+
assert even $a
98+
```
99+
```
132100
Error:
133101
× Assertion failed.
134102
╭─[entry #37:1:1]
@@ -138,29 +106,63 @@ Error:
138106
╰────
139107
```
140108

141-
## Test modules & test cases
109+
## Running the tests
142110

143-
The naming convention for test modules is `test_<your_module>.nu` and `test_<test name>` for test cases.
111+
Now that we are able to write tests by calling commands from `std assert`, it would be great to be able to run them and see our tests fail when there is an issue and pass when everything is correct :)
144112

145-
In order for a function to be recognized as a test by the test runner it needs to be annotated with `#[test]`.
146113

147-
The following annotations are supported by the test runner:
114+
### Nupm package
148115

149-
- test - test case to be executed during test run
150-
- test-skip - test case to be skipped during test run
151-
- before-all - function to run at the beginning of test run. Returns a global context record that is piped into every test function
152-
- before-each - function to run before every test case. Returns a per-test context record that is merged with global context and piped into test functions
153-
- after-each - function to run after every test case. Receives the context record just like the test cases
154-
- after-all - function to run after all test cases have been executed. Receives the global context record
116+
In this first case, we will assume that the code you are trying to test is part of a [Nupm] package.
155117

156-
The standard library itself is tested with this framework, so you can find many examples in the [Nushell repository](https://github.com/nushell/nushell/blob/main/crates/nu-std/tests/).
118+
In that case, it is as easy as following the following steps
119+
- create a `tests/` directory next to the `nupm.nuon` package file of your package
120+
- make the `tests/` directory a valid module by adding a `mod.nu` file into it
121+
- write commands inside `tests/`
122+
- call `nupm test`
157123

158-
## Setting verbosity
124+
The convention is that any command fully exported from the `tests` module will be run as a test, e.g.
125+
- `export def some-test` in `tests/mod.nu` will run
126+
- `def just-an-internal-cmd` in `tests/mod.nu` will NOT run
127+
- `export def another-test` in `tests/spam.nu` will run if and only if there is something like `export use spam.nu *` in `tests/mod.nu`
159128

160-
The `testing.nu` module uses the `log` commands from the standard library to display information, so you can set `NU_LOG_LEVEL` if you want more or less details:
161129

162-
```nu
163-
❯ use testing.nu run-tests
164-
❯ NU_LOG_LEVEL=DEBUG run-tests
165-
❯ NU_LOG_LEVEL=WARNING run-tests
130+
### Standalone tests
131+
132+
If your Nushell script or module is not part of a [Nupm] package, the simplest way is to write tests in standalone scripts and then call them, either from a `Makefile` or in a CI:
133+
134+
Let's say we have a simple `math.nu` module which contains a simple Fibonacci command:
135+
```nushell
136+
# `fib n` is the n-th Fibonacci number
137+
export def fib [n: int] [ nothing -> int ] {
138+
if $n == 0 {
139+
return 0
140+
} else if $n == 1 {
141+
return 1
142+
}
143+
144+
(fib ($n - 1)) + (fib ($n - 2))
145+
}
166146
```
147+
then a test script called `tests.nu` could look like
148+
```nushell
149+
use math.nu fib
150+
use std assert
151+
152+
for t in [
153+
[input, expected];
154+
[0, 0],
155+
[1, 1],
156+
[2, 1],
157+
[3, 2],
158+
[4, 3],
159+
[5, 5],
160+
[6, 8],
161+
[7, 13],
162+
] {
163+
assert equal (fib $t.input) $t.expected
164+
}
165+
```
166+
and be invoked as `nu tests.nu`
167+
168+
[Nupm]: https://github.com/nushell/nupm

0 commit comments

Comments
 (0)