|
3 | 3 | Found an issue with an exercise, or have an idea for a new one? Open an issue first before writing any code — it saves everyone rework if the idea doesn't fit the curriculum sequence. |
4 | 4 |
|
5 | 5 | ## How tests look like for beginner exercises |
6 | | -Let's look at the test file first: |
7 | | - |
8 | 6 | ```python |
9 | 7 | def test_prints_hello_world(run_script): |
10 | 8 | stdout, _ = run_script("hello_world.py") |
11 | 9 | assert stdout.strip() == "Hello, World!" |
12 | 10 | ``` |
13 | 11 |
|
14 | | -`run_script` is a helper set up once for the whole repo. It runs `hello_world.py` exactly the way Python would if you typed `python hello_world.py`, and hands back everything it printed. The test then checks that what got printed matches `'Hello, World!'` exactly — capitalization and punctuation included. |
| 12 | +### Fixtures |
| 13 | +Raw pytest failures (`IndexError`, `assert '' == 'Hello, World!'`) are confusing before you know what a list or an assertion diff even is. Script-style tests instead use four shared helpers from `conftest.py`. |
| 14 | + |
| 15 | +We have implemented a few custom functions (*fixtures*) built to make errors less scary for students. Please use these until later exercises where students cover specific erorrs and how to handle them. |
15 | 16 |
|
| 17 | +* `run_script` - Run a .py file exactly as `python <file>` would, and capture its output. |
| 18 | +* `expect_output` - Assert a script's entire printed output matches exactly, with a |
| 19 | + plain-language failure message instead of pytest's default diff. |
| 20 | +* `expect_line` - Assert that a specific (1-indexed) line of a script's output matches |
| 21 | + exactly, with a plain-language message — including when the script |
| 22 | + hasn't printed that many lines yet, instead of a raw `IndexError`. |
| 23 | +* `expect_variables` - Assert that several variables in a script's namespace all match |
| 24 | + expected values at once, with a single friendly summary of everything |
| 25 | + that's missing or wrong — instead of stopping at the first problem |
| 26 | + like `expect_variable` does. |
| 27 | +* `expect_variable_type` - Assert that a variable exists and is of a given type, without caring |
| 28 | + about its exact value — e.g. "is this a string" rather than "is this |
| 29 | + exactly 'hello'". |
| 30 | + |
| 31 | +You can find more about these functions in their docstrings in [conftest.py file](https://github.com/ThePythonLedger/python-exercises/blob/main/conftest.py) |
16 | 32 |
|
17 | 33 | ## Two exercise styles |
18 | 34 |
|
19 | 35 | Early exercises don't assume you know about functions yet, so they're written as **plain scripts** — top-level code you'd type straight into the terminal, no `def` or `import`. Their tests use a shared `run_script` fixture (see `conftest.py`) that runs the file exactly like `python <file>.py` and checks what it printed. |
20 | 36 |
|
21 | 37 | Once functions show up in the curriculum, exercises switch to the **function style** you may be more used to seeing: a stub function you fill in, imported directly into the test file. Each exercise's README says which style it is, but you can also tell from the stub file itself — a bare script vs. a `def`. |
22 | 38 |
|
23 | | -## Friendly failure messages |
24 | | - |
25 | | -Raw pytest failures (`IndexError`, `assert '' == 'Hello, World!'`) are confusing before you know what a list or an assertion diff even is. Script-style tests instead use two shared helpers from `conftest.py`: |
26 | | - |
27 | | -- `expect_output(stdout, "expected text")` — checks the whole printed output at once, and on failure says plainly what was expected vs. what actually printed. |
28 | | -- `expect_line(stdout, line_number, "expected text")` — checks one line of output (1-indexed). If the script hasn't printed that many lines yet, it says so directly ("Have you added a print() statement for this part yet?") instead of raising `IndexError`. |
29 | | - |
30 | | -Both suppress the Python traceback entirely — the learner only sees the message, not internal test code. |
31 | | - |
32 | 39 | ## Running everything at once |
33 | 40 |
|
34 | 41 | From the repo root, `pytest` (no arguments) will discover and run every non-skipped test in the repo — handy as a sanity check, but exercises are meant to be done one at a time. |
|
0 commit comments