class Doubler:
def __init__(self, n):
self._n = 2 * n
def n(self):
return self._n
x = Doubler(5)
print(x.n() == 10)
assert(x.n() == 10)
y = Doubler(-4)
print(y.n() == -8)
assert(y.n() == -8)
True
True
February 5, 2024
Doubler
?class Doubler:
def __init__(self, n):
self._n = 2 * n
def n(self):
return self._n
x = Doubler(5)
print(x.n() == 10)
assert(x.n() == 10)
y = Doubler(-4)
print(y.n() == -8)
assert(y.n() == -8)
True
True
Doubler
classprint
or assert
statements?print
statements require manual checking of outputassert
statements automatically checks correctnesspytest
or unittest
coverage.py
unittest
for DayOfTheWeek
import unittest
from dayoftheweek import DayOfTheWeek
class TestDayOfTheWeek(unittest.TestCase):
def test_init(self):
d = DayOfTheWeek('F')
self.assertEqual(d.name(), 'Friday')
d = DayOfTheWeek('Th')
self.assertEqual(d.name(), 'Thursday')
unittest.main(argv=['ignored'], verbosity=2, exit=False)
<unittest.main.TestProgram at 0x7f8528a78040>
unittest.main
differently for tests outside Quartotest_dayoftheweek.py
in slides/weekfour/
OK
output confirms that the assertions passedDayOfTheWeek
class DayOfTheWeek:
"""A class to represent a day of the week."""
def __init__(self, abbreviation):
"""Create a new DayOfTheWeek object."""
self.abbreviation = abbreviation
self.name_map = {
"M": "Monday",
"T": "Tuesday",
"W": "Wednesday",
"Th": "Thursday",
"F": "Friday",
"Sa": "Saturday",
"Su": "Sunday",
}
def name(self):
return self.name_map.get(self.abbreviation)
L1 = [1, 2, 3, 4, 5]
L2 = [6, 7, 8, 9, 10]
avg1 = sum(L1)/len(L1)
avg2 = sum(L2)/len(L2)
print("avg(", L1, ") -->", avg1)
print("avg(", L2, ") -->", avg2)
avg( [1, 2, 3, 4, 5] ) --> 3.0
avg( [6, 7, 8, 9, 10] ) --> 8.0
L1 = [1, 2, 3, 4, 5]
L2 = [6, 7, 8, 9, 10]
if len(L1) == 0:
avg1 = 0
else:
avg1 = sum(L1) / len(L1)
if len(L2) == 0:
avg2 = 0
else:
avg2 = sum(L2) / len(L2)
print("avg(", L1, ") -->", avg1)
print("avg(", L2, ") -->", avg2)
avg( [1, 2, 3, 4, 5] ) --> 3.0
avg( [6, 7, 8, 9, 10] ) --> 8.0
def avg(L):
if len(L) == 0:
return 0
else:
return sum(L) / len(L)
L1 = [1, 2, 3, 4, 5]
L2 = [6, 7, 8, 9, 10]
avg1 = avg(L1)
avg2 = avg(L2)
print("avg(", L1, ") -->", avg1)
print("avg(", L2, ") -->", avg2)
avg( [1, 2, 3, 4, 5] ) --> 3.0
avg( [6, 7, 8, 9, 10] ) --> 8.0
avg
function avoids the defect and is easier to read!coverage.py
Software testing helps to refine an object-oriented design
Interplay between testing and object-oriented design:
pytest
hypothesis
coverage.py
mutmut
DayOfTheWeek
with Pytestfrom daydetector.dayoftheweek import DayOfTheWeek
def test_init():
"""Test the DayOfTheWeek class."""
d = DayOfTheWeek("F")
assert d.name() == "Friday"
d = DayOfTheWeek("Th")
assert d.name() == "Thursday"
d = DayOfTheWeek("W")
assert d.name() == "Wednesday"
d = DayOfTheWeek("T")
assert d.name() == "Tuesday"
d = DayOfTheWeek("M")
assert d.name() == "Monday"
Run poetry run pytest
in the daydetector
directory
pytest
will automatically discover and run the tests!
pytest
@pytest.mark.parametrize(
"abbreviation, expected",
[
("M", "Monday"),
("T", "Tuesday"),
("W", "Wednesday"),
("Th", "Thursday"),
("F", "Friday"),
("Sa", "Saturday"),
("Su", "Sunday"),
("X", None),
],
)
def test_day_name(abbreviation, expected):
"""Use parameterized testing to confirm that lookup works correctly."""
day = DayOfTheWeek(abbreviation)
assert day.name() == expected
import hypothesis.strategies as st
from hypothesis import given
import pytest
@pytest.mark.parametrize(
"valid_days",
[["Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday"]],
)
@given(
st.text(alphabet=st.characters(), min_size=1, max_size=2)
)
def test_abbreviation_maps_to_name(valid_days, abbreviation):
"""Use property-based testing with Hypothesis to confirm mapping."""
day = DayOfTheWeek(abbreviation)
assert day.name() in valid_days or day.name() is None
Hypothesis
strategies generate random character inputs for the abbreviation
parameter, thereby increasing the input diversitycoverage.py
tests/test_dayoftheweek.py .......... [100%]
---------- coverage: platform linux, python 3.11.6-final-0 -----------
Name Stmts Miss Branch BrPart Cover
---------------------------------------------------------------
daydetector/__init__.py 0 0 0 0 100%
daydetector/dayoftheweek.py 6 0 0 0 100%
---------------------------------------------------------------
TOTAL 6 0 0 0 100%
10 passed in 0.21s
The test suite covers 100%
of the program’s source code
Covering statements increases the likelihood of detecting defects
Do high coverage tests mean that the program is defect-free?
mutmut
--- daydetector/dayoftheweek.py
+++ daydetector/dayoftheweek.py
@@ -7,15 +7,7 @@
def __init__(self, abbreviation):
"""Create a new DayOfTheWeek object."""
self.abbreviation = abbreviation
- self.name_map = {
- "M": "Monday",
- "T": "Tuesday",
- "W": "Wednesday",
- "Th": "Thursday",
- "F": "Friday",
- "Sa": "Saturday",
- "Su": "Sunday",
- }
+ self.name_map = None
mutmut
changes the program to see if tests find possible defect
Mutation operators systematically modify the program’s code
www.algorithmology.org/slides/weekfour/
poetry install
poetry run pytest
poetry run pytest --cov --cov-branch
poetry run mutmut run
poetry run mutmut show 16
test_dayoftheweek.py
were written by a large language model?What are the benefits and downsides of using artificial intelligence (AI) to generate tests?
What are situations in which you should and should not use AI to generate tests?
Algorithmology