Verify code is working as expected.
Simplest way to test is to run code and check output.
Automated testing checks output automatically.
Code changes can break other parts of code.
Automatic testing verifies code is still working.
Definition “Unit”
pytest-cov
pytest --cov=myproj
Test edge cases
def test_operable_period_can_be_missing():
assert is_operable(height=1.0, period=None)
assert is_operable(height=1.0, period=np.nan)
assert is_operable(height=1.0)
assert not is_operable(height=11.0)
def test_height_can_not_be_missing():
with pytest.raises(ValueError) as excinfo:
is_operable(height=None)
is_operable(height=np.nan)
assert "height" in str(excinfo.value)
The benefit of this approach is that you are forced to think about the expected behaviour of your code before you write it.
It is also too easy to write a test that passes without actually testing the code.
and now for something completely different…
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
…
Errors should never pass silently.
Unless explicitly silenced.
…
IndexError
KeyError
ValueError
FileNotFoundError
ModelInitialistionError
, MissingLicenseError
?src/ops.py
It is better to raise an exception (that can terminate the program), than to propagate a bad value.
Warnings are a way to alert users of your code to potential issues or usage errors without actually halting the program’s execution.
tests/test_ops.py
The same can be done with warnings.
A way to check your code for common errors and style issues.
ruff
is a new tool for linting Python code.
examples/04_testing/process.py
Run ruff check
:
$ ruff check process.py
process.py:1:8: F401 [*] `requests` imported but unused
process.py:6:12: F821 Undefined name `np`
process.py:7:5: F841 [*] Local variable `method` is assigned to but never used
Found 3 errors.
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
ruff
can be used for code formatting (in addition to linting).ruff
is a faster replacement of black
, a previously commonly used code formatter.Check if files need formatting (optional):
$ ruff format --check
Would reformat: data_utils.py
Would reformat: dfsu/__init__.py
Would reformat: dataarray.py
Would reformat: dataset.py
Would reformat: spatial/geometry.py
Would reformat: pfs/pfssection.py
6 files would be reformatted
Format files:
Visual Studio Code can be configured to run ruff format
automatically when saving a file using the Ruff
extension.
line_profiler
package reports the time spent on each line of code.lprun
magic command.import numpy as np
def top_neighbors(points, radius="0.1"):
"""Don't use this function, it's only purpose is to be profiled."""
n = len(points)
idx = np.array([int(x) for x in str.split("0 "* n)])
for i in range(n):
for j in range(n):
if i != j:
d = np.sqrt(np.sum((points[i] - points[j])**2))
if d < float(radius):
idx[i] += 1
for i in range(n):
for j in range(n - i - 1):
if idx[j] < idx[j + 1]:
idx[j], idx[j + 1] = idx[j + 1], idx[j]
points[j], points[j + 1] = points[j + 1], points[j]
return points
def main():
points = np.random.rand(1000, 2)
top = top_neighbors(points)
Invoking the jupyter magic command lprun
with:
top_neighbors
main()
Line # Hits Time Per Hit % Time Line Contents
==============================================================
3 def top_neighbors(points, radius="0.1"):
4 """Don't use this function, it's only purpose is to be profiled."""
5 1 2800.0 2800.0 0.0 n = len(points)
6 1 353300.0 353300.0 0.0 idx = np.array([int(x) for x in str.split("0 "* n)])
7
8 1001 345100.0 344.8 0.0 for i in range(n):
9 1001000 378191701.0 377.8 2.2 for j in range(n):
10 1000000 328387205.0 328.4 1.9 if i != j:
11 999000 1e+10 14473.0 83.8 d = np.sqrt(np.sum((points[i] - points[j])**2))
12 999000 933778605.0 934.7 5.4 if d < float(radius):
13 28952 57010001.0 1969.1 0.3 idx[i] += 1
14 1001 367100.0 366.7 0.0 for i in range(n):
15 500500 144295203.0 288.3 0.8 for j in range(n - i - 1):
16 499500 302166901.0 604.9 1.8 if idx[j] < idx[j + 1]:
17 240227 212070500.0 882.8 1.2 idx[j], idx[j + 1] = idx[j + 1], idx[j]
18 240227 437538803.0 1821.4 2.5 points[j], points[j + 1] = points[j + 1], points[j]
19 1 500.0 500.0 0.0 return points
Python package development