! TASK 1 ! Write a new test case that tests the daily_max()
function, adding it to test/test_models.py
. Also regenerate your requirements.txt
file, commit your changes, and merge the test-suite
branch with the develop
branch. (5-10 min)
daily_mean()
, defining input and expected output variables followed by the equality assertion.python -m pytest tests/test_models.py
, and have a look at your new tests pass.Solution (examples)
def test_daily_max_integers():
"""Test that max function works for an array of positive integers."""
from inflammation.models import daily_max
test_input = np.array([[4, 2, 5],
[1, 6, 2],
[4, 1, 9]])
test_result = np.array([4, 6, 9])
npt.assert_array_equal(daily_max(test_input), test_result)
One can also check cases where seeing an error is the correct behaviour (we need to import pytest
to be able to execute pytest.raises()
):
import pytest
...
def test_daily_max_string():
"""Test for TypeError when passing strings"""
from inflammation.models import daily_max
with pytest.raises(TypeError):
error_expected = daily_max([['Hi', 'there'], ['Alfe', 'folks']])
We regenerate our requirements.txt
file, commit our changes, and merge the test-suite
branch with the develop
branch.
$ pip3 freeze > requirements.txt
$ git add requirements.txt tests/test_models.py # alternative: git add ./
$ git commit -m "Add initial test cases for daily_max() and daily_min()"
$ git checkout develop
$ git merge test-suite
! TASK 2 ! Rewrite your test functions for daily_max()
using test parameterisation. (5-10 min)
test-suite
branch.python -m pytest tests/test_models.py
, and have a look at your new tests pass.test-suite
branch with the develop
branch.Solution (examples)
@pytest.mark.parametrize(
"test, expected",
[
([ [0, 0], [0, 0], [0, 0] ], [0, 0]),
([ [1, 2], [3, 4], [5, 6] ], [5, 6]),
])
def test_daily_max(test, expected):
"""Test mean function works for array of zeroes and positive integers."""
from inflammation.models import daily_max
npt.assert_array_equal(daily_max(np.array(test)), np.array(expected))
We can also incorporate the case where seeing an error is the correct behaviour by including a third argument name indicating the expected error raises (e.g., expect_raises
), as well as the expected error raises (None
for where we don't expect to see any):
@pytest.mark.parametrize(
"test, expected, expect_raises",
[
([ [0, 0], [0, 0], [0, 0] ], [0, 0], None),
([ [1, 2], [3, 4], [5, 6] ], [5, 6], None),
([['Hi', 'there'], ['ALife', 'folks']], ['Whats', 'up'] , TypeError)
])
def test_daily_max(test, expected, expect_raises):
"""Test mean function works for array of zeroes and positive integers."""
from inflammation.models import daily_max
if expect_raises is not None:
with pytest.raises(TypeError):
daily_max([['Hi', 'there'], ['ALife', 'folks']])
else:
npt.assert_array_equal(daily_max(np.array(test)), np.array(expected))
! QUESTION 1 ! What outputs do you expect here?
Alice = Patient('Alice')
print(Alice)
obs = Alice.add_observation(3)
print(obs)
Bob = Person('Bob')
print(Bob)
obs = Bob.add_observation(4)
print(obs)
Output:
Alice
3
Bob
AttributeError: 'Person' object has no attribute 'add_observation'
An error is thrown because we cannot add an observation to bob, who is a Person but not a Patient.
! TASK 3 ! Write a Doctor class to hold the data representing a single doctor:
test_patient.py
.
class Doctor(Person):
"""A doctor in an inflammation study."""
def __init__(self, name):
super().__init__(name)
self.patients = []
def add_patient(self, new_patient):
# A crude check by name if this patient is already looked after
# by this doctor before adding them
for patient in self.patients:
if patient.name == new_patient.name:
return
self.patients.append(new_patient)
def __str__(self):
return {'name': self.name, 'patients': self.patients}
self.patients
as another attribute (an empty list at the start).Possible tests:
def test_add_patient_to_doctor():
"""Check patients are being added correctly by a doctor. """
from inflammation.models import Doctor, Patient
doc = Doctor("Sheila Wheels")
new_patient = Patient("Bob")
doc.add_patient(new_patient)
assert doc.patients is not None
assert len(doc.patients) == 1
doc.patients
is neither None
, nor empty.
def test_doctor_is_person():
"""Check if a doctor is a person."""
from inflammation.models import Doctor, Person
doc = Doctor("Sheila Wheels")
assert isinstance(doc, Person)
isinstance()
.! QUESTION 2 ! Which of these functions are pure?
def add_one(x):
return x + 1
def say_hello(name):
print('Hello', name)
def append_item_1(a_list, item):
a_list += [item]
return a_list
def append_item_2(a_list, item):
result = a_list + [item]
return result
add_one
is pure - it has no effects other than to return a value and this value will always be the same when given the same inputs.say_hello
is not pure - printing text is a side effect.append_item_1
is not pure - the argument a_list
gets modified as a side effect.append_item_2
is pure - result
is a new variable, so this time a_list
does not get modified.! TASK 4 ! Write a decorator that measures the time time taken to execute a particular function using the time.process_time_ns() function.
time
.start = time.process_time_ns()
, and get another time stamp once the calculation in question is done using end = time.process_time_ns()
.
def measure_me(n):
total = 0
for i in range(n):
total += i * i
return total
Solution (example):
import time
def profile(func):
def inner(*args, **kwargs):
start = time.process_time_ns()
result = func(*args, **kwargs)
stop = time.process_time_ns()
print("Took {0} seconds".format((stop - start) / 1e9))
return result
return inner
@profile
def measure_me(n):
total = 0
for i in range(n):
total += i * i
return total
print(measure_me(1000000))
Output
Took 0.124199753 seconds
333332833333500000