Skip to content

Commit 7763ee6

Browse files
committed
Add unit tests
1 parent b2f5f7d commit 7763ee6

File tree

4 files changed

+245
-0
lines changed

4 files changed

+245
-0
lines changed

12 Testing Your Code.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
parent: Programming for Modelling and Data Analysis
3+
has_children: true
4+
nav_order: 12
5+
---
6+
7+
# Testing Your Code
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
---
2+
parent: "Testing Your Code"
3+
grand_parent: Programming for Modelling and Data Analysis
4+
nav_order: 1
5+
---
6+
7+
# Writing unit tests
8+
9+
## Why unit tests?
10+
11+
When writing code, it is important to ensure that it works as expected. One way to do this is by writing *unit tests*. Unit tests are small pieces of code that test individual units or components of your code to verify that they behave as intended.
12+
13+
Unit tests are important for several reasons:
14+
1. **Catch Bugs Early**: By testing individual components, you can catch bugs early in the development process, making them easier and cheaper to fix.
15+
2. **Facilitate Change**: Unit tests provide a safety net when making changes to your code. If you modify a component and the tests fail, you know immediately that something is wrong.
16+
3. **Documentation**: Unit tests can serve as documentation for your code. They show how the code is intended to be used and what its expected behavior is.
17+
4. **Improve Design**: Writing tests can help you think about your code's design and architecture, leading to better-structured and more maintainable code.
18+
19+
## `unittest` module
20+
21+
In Python, you can use the built-in `unittest` framework to write and run unit tests. Here's a simple example to illustrate how to create and run unit tests.
22+
23+
```python
24+
import unittest
25+
26+
def is_even(x: int) -> bool:
27+
return x % 2 == 0
28+
29+
class TestIsEven(unittest.TestCase):
30+
def test_even_number_returns_true(self):
31+
self.assertTrue(is_even(4))
32+
33+
def test_odd_number_returns_false(self):
34+
self.assertFalse(is_even(5))
35+
36+
if __name__ == "__main__":
37+
unittest.main() # allows running this file directly
38+
```
39+
40+
In this example, we define a simple function `is_even` that checks if a number is even. We then create a test case class `TestIsEven` that inherits from `unittest.TestCase`. Inside this class, we define two test methods: `test_even_number_returns_true` and `test_odd_number_returns_false`, which test the behavior of the `is_even` function.
41+
42+
To run the tests, you can execute the script directly, and the `unittest` framework will run all the test methods defined in the `TestIsEven` class.
43+
44+
## Organizing unit tests
45+
46+
Most tests written with the `unittest` framework are organized as classes that derive from `unittest.TestCase`.
47+
48+
- Group related tests into a `Test...` class. The class name should start with `Test` and describe what is being tested, for example `class TestMathUtils(unittest.TestCase):` or `class TestStringHelpers(unittest.TestCase):`.
49+
- Put individual checks into methods whose names start with `test_`. The test runner discovers and runs any method beginning with `test_`.
50+
- Keep each test method focused: one logical assertion or behavior per test method. This makes failures easier to interpret.
51+
- Name test methods clearly to explain the expected behavior, for example `test_add_positive_numbers`, `test_raises_on_invalid_input`, `test_returns_empty_list_when_no_items`.
52+
- Avoid putting multiple unrelated assertions in a single test method — if you must, prefer splitting into several test methods.
53+
- Keep tests independent: they should not rely on global state left by other tests.
54+
55+
Example layout inside a package:
56+
57+
```
58+
project/
59+
package/
60+
utils.py
61+
other.py
62+
tests/
63+
test_utils.py # contains TestUtils (Test... classes and test_ methods)
64+
test_other.py
65+
```
66+
67+
With this structure and clear naming, test discovery is reliable and the VS Code test explorer and other tools will show readable test names that map back to your code.
68+
69+
70+
## Available assertions
71+
72+
The `unittest.TestCase` class provides several assertion methods to check for different conditions in your tests. Here are some commonly used assertions:
73+
74+
- `self.assertEqual(a, b)`: Check that `a` is equal to `b`.
75+
- `self.assertNotEqual(a, b)`: Check that `a` is not equal to `b`.
76+
- `self.assertTrue(x)`: Check that `x` is `True`.
77+
- `self.assertFalse(x)`: Check that `x` is `False`.
78+
- `self.assertIsNone(x)`: Check that `x` is `None`.
79+
- `self.assertIsNotNone(x)`: Check that `x` is not `None`.
80+
- `self.assertIn(a, b)`: Check that `a` is in `b`.
81+
- `self.assertNotIn(a, b)`: Check that `a` is not in `b`.
82+
- `self.assertRaises(exception, callable, *args, **kwargs)`: Check that calling `callable` with the given arguments raises the specified `exception`.
83+
84+
Below are short examples that show how the most common assertions are used in practice.
85+
86+
```python
87+
class TestAssertions(unittest.TestCase):
88+
def test_equal_and_not_equal(self):
89+
self.assertEqual(2 + 2, 4)
90+
self.assertNotEqual(2 + 2, 5)
91+
92+
def test_true_false(self):
93+
self.assertTrue(1 < 2)
94+
self.assertFalse(1 > 2)
95+
96+
def test_is_none(self):
97+
x = None
98+
self.assertIsNone(x)
99+
100+
def test_in_and_not_in(self):
101+
self.assertIn('py', 'python')
102+
self.assertNotIn('java', 'python')
103+
104+
def test_combined_examples(self):
105+
items = [1, 2, 3]
106+
self.assertEqual(len(items), 3)
107+
self.assertTrue(isinstance(items, list))
108+
109+
```
110+
111+
### Using assertRaises
112+
113+
Testing that code raises the expected exception is common. `unittest` provides two ways to assert exceptions:
114+
115+
1. The context-manager form (preferred when you want to run a block of code and optionally inspect the exception object).
116+
2. The callable form, where you pass the callable and its arguments to `assertRaises`.
117+
118+
Examples:
119+
120+
```python
121+
class TestExceptions(unittest.TestCase):
122+
# Context manager form (recommended for readability)
123+
def test_divide_by_zero_context(self):
124+
with self.assertRaises(ZeroDivisionError) as cm:
125+
_ = 1 / 0
126+
# optional: inspect the exception
127+
self.assertIn('division', str(cm.exception))
128+
129+
# Callable form
130+
def test_divide_by_zero_callable(self):
131+
132+
def divide_by_zero():
133+
return 1 / 0
134+
135+
self.assertRaises(ZeroDivisionError, divide_by_zero)
136+
137+
# Another example: check that invalid index raises IndexError
138+
def test_index_error(self):
139+
lst = []
140+
with self.assertRaises(IndexError):
141+
_ = lst[0]
142+
143+
```
144+
145+
Notes and tips:
146+
- Prefer the context-manager form when you need to run several lines and/or inspect the exception object.
147+
- Use the callable form for short one-liners when that is clearer.
148+
- Be explicit about the specific exception you expect — don't assert a broad Exception unless truly necessary.
149+
150+
151+
152+
<hr/>
153+
154+
Published under [Creative Commons Attribution-NonCommercial-ShareAlike](https://creativecommons.org/licenses/by-nc-sa/4.0/) license.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
parent: "Testing Your Code"
3+
grand_parent: Programming for Modelling and Data Analysis
4+
nav_order: 2
5+
---
6+
7+
# Running unit tests
8+
9+
## Running unit tests from the command line
10+
11+
To run unit tests, you can use the command line. If you have a file named `test_is_even.py` containing the example tests, you can run the tests using the following command:
12+
13+
```bash
14+
python -m unittest test_is_even.py
15+
```
16+
17+
This will execute all the test methods in the `TestIsEven` class and report the results.
18+
19+
## Configuring tests in Visual Studio Code
20+
21+
Visual Studio Code has built-in test discovery and a test runner that integrates with Python's `unittest`, `pytest` and `nose` frameworks. The steps below show how to configure VS Code to discover and run `unittest` tests (the built-in framework) and a recommended file naming scheme.
22+
23+
1. Open the Command Palette (Ctrl+Shift+P / Cmd+Shift+P) and choose "Python: Configure Tests".
24+
2. Select the test framework you want to use (choose "unittest").
25+
3. Choose the folder to use as the test root (usually the workspace root).
26+
27+
VS Code will then add the necessary settings to your workspace. You can also add or edit those settings manually in `.vscode/settings.json`. Example settings that make test discovery consistent:
28+
29+
```json
30+
{
31+
"python.testing.unittestEnabled": true,
32+
"python.testing.pytestEnabled": false,
33+
"python.testing.nosetestsEnabled": false,
34+
"python.testing.unittestArgs": [
35+
"-v",
36+
"-s",
37+
"",
38+
"-p",
39+
"test*.py"
40+
]
41+
}
42+
```
43+
44+
Notes about the example:
45+
- The `-s` argument controls the start directory for discovery; leaving it as an empty string uses the workspace root. If you put a relative path there (for example `tests`), discovery starts from that folder.
46+
- The pattern `test*.py` tells the test discovery to look for files starting with `test` and ending with `.py`.
47+
48+
### Recommended file naming and layout
49+
50+
To make discovery reliable and keep tests organized, follow a simple naming scheme and layout:
51+
52+
- Place tests either alongside the code under test (same package) or in a top-level `tests/` folder.
53+
- Name test files using one of these patterns:
54+
- `test_<module>.py` (e.g. `test_utils.py`) — common and recommended
55+
- `<module>_test.py` — also supported by many tools
56+
- Name test classes using the `Test...` prefix (for `unittest.TestCase` subclasses), for example `class TestMathUtils(unittest.TestCase):`.
57+
- Name individual test methods starting with `test_` so the test runner recognises them, e.g. `def test_add_positive_numbers(self):`.
58+
59+
Example layout (recommended):
60+
61+
- project/
62+
- package/
63+
- utils.py
64+
- other.py
65+
- tests/
66+
- test_utils.py
67+
- test_other.py
68+
69+
With this layout and the `.vscode/settings.json` discovery pattern above, VS Code will discover the tests and show them in the Testing sidebar. You can run or debug single tests from the editor or the sidebar.
70+
71+
### Running and debugging in VS Code
72+
73+
- Use the Testing view (the beaker icon) to run or debug tests interactively.
74+
- Hover over a discovered test in the editor to see Run and Debug codelenses.
75+
- Use the output panel and Test Log to inspect failures and stack traces.
76+
77+
78+
<hr/>
79+
80+
Published under [Creative Commons Attribution-NonCommercial-ShareAlike](https://creativecommons.org/licenses/by-nc-sa/4.0/) license.

index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,7 @@ Project 1: [Hangman](https://github.com/mds-python/hangman)
9696
Project 2: [Game of Life](https://github.com/mds-python/game-of-life)
9797

9898
Project 3: [COVID Simulator](https://github.com/mds-python/covid)
99+
100+
## Testing your code
101+
* [Writing unit tests](12%20Testing%20your%20code/1%20Writing%20unit%20tests)
102+
* [Running unit tests](12%20Testing%20your%20code/2%20Running%20unit%20tests)

0 commit comments

Comments
 (0)