Automated Testing with Pytest
Author
Manual testing is an integral part of software development. However, it does not scale well with the size of a project. The more your codebase grows the more time you have to spend testing. What’s more, as things become more complex it becomes difficult to understanded all the interactions between different part of your code and it is easy to break one part of your program by modifying another one. This is where automated testing are invaluable. By building a suite of tests you can constantly verify that your code works as expected and that new features are being integrated properly!
import random
import json
from psychopy.sound import Sound
from psychopy.visual import Window
from psychopy.event import waitKeys
import pytest
import ipytest
ipytest.autoconfig()
Sound(stereo=False);Section 1: Writing Assert Statements
A key component of every test is the assert statement.
It tests that result of some part of the code is what you would expect.
assert is really simple - it raises an AssertionError if whateber you are asserting evaluates to False and does nothing otherwise.
Generally, more asserts are better but often it is a good idea to focus your test on interesting edge cases, like: what happens when you pass in an empty list to a function that accepts lists?
What if a time parameter is zero or negative? And so on …
| Code | Duration |
|---|---|
assert a == b |
Raise an AssertionError if a is NOT equal to b, otherwise do nothing |
assert isinstance(a, int) |
Raise an AssertionError if a is not an integer, otherwise do nothing |
assert a > b |
Raise an AssertionError if a is NOT greater than b, otherwise do nothing |
tone = Sound(value=350, secs=0.5) |
Create a tone at 350Hz with a duration of 0.5 seconds |
key = waitKeys(keyList="space", timeStamped=True) |
Wait until the "space" key was pressed and return a list of lists with [[name, time]]. |
Exercises
Example: Write an assert statement to check that
- the value
xbelow is positive
and print "Success"!.
x = random.random();assert x > 0
"Success!"'Success!'Exercise: Write an assert statment to check that
- the absolute difference between x and y is smaller than 1
and and print "Success"!.
x = random.random()
y = random.random();Solution
assert abs(x - y) < 1
"Success!"'Success!'Exercise: Write assert statements to check that
- the length of
xis 3 - the first element
x[0]is a string
and print "Success"!
x = ["1", "2", "3"]Solution
assert len(x) == 3
assert isinstance(x[0], str)
"Success!"'Success!'Exercise: Write two assert statements that check that:
- the value returned by
subtract(3, 5)is-2 - the value returned by
subtract(10, 7)is3
and print "Success"!.
def subtract(a,b):
return a-bSolution
assert subtract(3, 5) == -2
assert subtract(10, 7) == 3
"Success!"Exercise: Write an assert statement to check that
- the the length of the list returned by
concatenate_lists(a,b)is equal tolen(a) + len(b)
and print "Success"!.
def concatenate_lists(a,b):
return a+b
a = [1, 2, 3]
b = [1, 2]Solution
assert len(concatenate_lists(a, b)) == len(a) + len(b)
"Success!"'Success!'Exercise: Write an assert statement that check that:
- the tone returned by
make_tone(freq=800, dur=0.5)has the attributestone.sound=800andtone.secs=0.5
and print "Success"!
def make_tone(freq, dur):
return Sound(value=freq, secs=dur)Solution
tone = make_tone(freq=800, dur=0.5)
assert tone.sound == 800
assert tone.secs == 0.5
"Success!"'Success!'Section 2: Writing Test Functions
Usually assert statments are not used on their own but wrapped inside test functions.
Organizing your tests into functions provides the same advantages as organizing your code in functions: it makes them more readable, maintainable and robust.
What’s more, it allow tools such as Pytest to do the testing automatically and report to you any failed tests.
In this section we are going to use Ipytest which is a convenient tool to use Pytest inside a Jupyter notebook: simply include an %%ipytest tag at the top of a cell and, when you execute it, Pytest will run every function in the cell that starts with test_.
The rules for writing good test functions are the same as the rules for writing good function: each test should have a clear purpose an a name that reflects this purpose like test_file_was_written().
| Code | Description |
|---|---|
%%ipytest |
Use Pytest to execute every function in a cell that starts with test_ |
with pytest.raises(ValueError): divide(1, "hi) |
Assert that divide(1,"hi") raises a ValueError |
First, we are going to test the trial_sequence() function defined below
Exercises
def trial_sequence(conditions: list, n_reps: int, shuffle:bool = True):
trials = conditions * n_reps
if shuffle:
random.shuffle(trials)
return trialsExample: Write a test function test_trial_sequence_is_shuffled() to tests that
- the returned list is shuffled when
shuffle=True
Add the %%ipytest tag to the top of the cell and run it to execute the test.
%%ipytest
def test_trial_sequence_is_shuffled():
trials1 = trial_sequence(conditions=[1,2,3], n_reps=1000, shuffle=True)
trials2 = trial_sequence(conditions=[1,2,3], n_reps=1000, shuffle=True)
assert trials1 != trials2[32m.[0m[32m [100%][0m
[32m[32m[1m1 passed[0m[32m in 0.00s[0m[0mExercise: Write a function test_trial_sequence_is_ordered() to tests that
- the returned list is ordered when
shuffle=False
Add the %%ipytest tag to the top of the cell and run it to execute the test.
Solution
%%ipytest
def test_trial_sequence_is_ordered():
trials1 = trial_sequence(conditions=[1,2,3], n_reps=1000, shuffle=False)
trials2 = trial_sequence(conditions=[1,2,3], n_reps=1000, shuffle=False)
assert trials1 == trials2[32m.[0m[32m [100%][0m
[32m[32m[1m1 passed[0m[32m in 0.00s[0m[0mExercise: Write a function test_trial_list_length() to tests that
- the
len()of the returned list is3000whenconditions=[1,2,3]andn_reps=1000 - the
len()of the returned list is0whenconditions=[]andn_reps=1000
Add the %%ipytest tag to the top of the cell and run it to execute the test.
Solution
%%ipytest
def test_trial_list_length():
trials1 = trial_sequence(conditions=[1,2,3], n_reps=1000, shuffle=True)
assert len(trials1) == 3000
trials2 = trial_sequence(conditions=[], n_reps=1000, shuffle=True)
assert len(trials2) == 0[32m.[0m[32m [100%][0m
[32m[32m[1m1 passed[0m[32m in 0.00s[0m[0mNext, we are going to test the load_config function defined in the cell below.
def load_config(fpath="config.json"):
with open(fpath) as f:
config = json.load(f)
return configExercise: Write a function test_config_has_keys() to tests that
- the returned dictionary contains the keys
"conditions"and"n_trials"
Add the %%ipytest tag to the top of the cell and run it to execute the test.
Solution
%%ipytest
def test_config_has_keys():
config = load_config()
assert "conditions" in config.keys()
assert "n_trials" in config.keys()[32m.[0m[32m [100%][0m
[32m[32m[1m1 passed[0m[32m in 0.00s[0m[0mExercise: Write a test_config_dtypes() function to test that
- the value stored under the key
"conditions"returned dictionary is alist - the value stored under the key
"n_trials"returned dictionary is anint
Add the %%ipytest tag to the top of the cell and run it to execute the test.
Solution
%%ipytes
def test_config_dtypes():
config = load_config()
assert isinstance(config["conditions"], list)
assert isinstance(config["n_trials"], int)[32m.[0m[32m [100%][0m
[32m[32m[1m1 passed[0m[32m in 0.00s[0m[0mExercise: Write a test_load_config_raises_error() function that uses pytest.raises to test that
load_configraises aFileNotFoundErrorwhen trying to load a file that does not exist
Add the %%ipytest tag to the top of the cell and run it to execute the test.
Solution
%%ipytest
def test_load_config_raises_error():
with pytest.raises(FileNotFoundError) as err:
load_config("Beep")[32m.[0m[32m [100%][0m
[32m[32m[1m1 passed[0m[32m in 0.00s[0m[0mSection 3: Runnig Pytest from the Terminal
Now that we understand how Pytest works we can start to develop our test module. The tests work exactly the same as in a notebook but you put them in a separate file. Usually this file is named like the file it tests with the “test_” prefix (e.g. test_my_module.py would contain the tests for my_module.py). In the test module, you can import the functions you want to test and then test them.
Organizing your test like this has the advantage that you can run all of them by simply calling pytest a single time.
Per default, Pytest will open every Python file that starts with test_ and, inside that file, run every function that starts with test_.
In the same folder as this notebook, create a new file called sequencegen.py, then copy the code below and save it to that file.
In the following exercises, we will test the functions from the sequencegen module!
Exercises
import random
def make_sequence(conditions, n_trials, min_dist=0, max_iter=1000):
n_reps = int(n_trials / len(conditions))
trials = conditions * n_reps
random.shuffle(trials)
count = 0
while has_repetitions(trials, min_dist):
random.shuffle(trials)
count += 1
if count >= max_iter:
raise StopIteration(f"Could not find a sequence after {count} iterations!")
return trials
def has_repetitions(trials, min_dist=1):
trial_is_repeat = []
trial_is_repeat.append(
len(set(trials[:min_dist])) < len(trials[:min_dist])
) # check beginning
for i in range(min_dist, len(trials)): # check rest of list
this_trial_is_repeat = []
for j in range(1, min_dist + 1):
this_trial_is_repeat.append(trials[i] == trials[i - j])
trial_is_repeat.append(any(this_trial_is_repeat))
return any(trial_is_repeat)
def save_sequence(trials, fname):
with open(fname, 'w') as f:
for i, t in enumerate(trials):
if i < len(trials) - 1:
f.write(str(t) + '\n')
else:
f.write(str(t)) # no newline for last elementExample: In the same folder as this noebook, create a new file called test_sequencegen.py and import the has_repetitions function from squencegen.
Write a test_has_repetitions() function to test that:
has_repetitions()returnsTruewhen passed a list with repetitionshas_repetitions()returnsFalsewhen passed a list without repetitions
Then, run !pytest to execute the tests.
Solution
# The content of test_sequencegen.py may look like this:
from sequencegen import has_repetitions
def test_has_repetitions():
assert has_repetitions([1,1,2,3]) == True
assert has_repetitions([1,2,3,1]) == False!pytest test_sequencegen.py::test_has_repetitions[1m============================= test session starts ==============================[0m
platform linux -- Python 3.12.12, pytest-8.4.2, pluggy-1.6.0
rootdir: /home/olebi/projects/new-learning-platform/notebooks/psychopy/03_modularization_and_testing/02_automated_testing
plugins: anyio-4.11.0
collected 1 item [0m
test_sequencegen.py [32m.[0m[32m [100%][0m
[32m============================== [32m[1m1 passed[0m[32m in 0.00s[0m[32m ===============================[0mExercise: add another function called test_has_repetitions_respects_min_dist to test that:
has_repetitions(x)withx=[1,2,3,1,2,3]is False whenmin_dist=1andmin_dist=2has_repetitions(x)withx=[1,2,3,1,2,3]is True whenmin_dist=3andmin_dist=4
Then, run !pytest to execute the tests.
Solution
# the function in test_sequencegen may look like this:
def test_has_repetitions_respects_min_dist():
x = [1,2,3,1,2,3]
assert has_repetitions(x, min_dist=1) == False
assert has_repetitions(x, min_dist=2) == False
assert has_repetitions(x, min_dist=3) == True
assert has_repetitions(x, min_dist=4) == True !pytest test_sequencegen.py::test_has_repetitions_respects_min_dist[1m============================= test session starts ==============================[0m
platform linux -- Python 3.12.12, pytest-8.4.2, pluggy-1.6.0
rootdir: /home/olebi/projects/new-learning-platform/notebooks/psychopy/03_modularization_and_testing/02_automated_testing
plugins: anyio-4.11.0
collected 1 item [0m
test_sequencegen.py [32m.[0m[32m [100%][0m
[32m============================== [32m[1m1 passed[0m[32m in 0.01s[0m[32m ===============================[0mExercises: Import make_sequence() from sequencegen in test_sequencegen.py and add a test_sequence_has_correct_len() function to test that
make_sequence()returns a list with 50 elements whenconditions=[1,2]andn_trials=50make_sequence()returns a list with 3 elements whenconditions=[1,2,3]andn_trials=3make_sequence()returns a list with 0 elements whenconditions=[]andn_trials=100
Then, run !pytest to execute the tests.
BONUS: What is the length of the returned list when conditions=[1,2] and n_trials=9?
Solution
# The function in test_sequence.py may look like this:
from sequencegen import has_repetitions, make_sequence
def test_sequence_has_correct_len():
assert len(make_sequence([1,2], 50)) == 50
assert len(make_sequence([1,2,3], 3)) == 3
# BONUS
assert len(make_sequence([1, 2], 9)) == 8!pytest test_sequencegen.py::test_sequence_has_correct_len[1m============================= test session starts ==============================[0m
platform linux -- Python 3.12.12, pytest-8.4.2, pluggy-1.6.0
rootdir: /home/olebi/projects/new-learning-platform/notebooks/psychopy/03_modularization_and_testing/02_automated_testing
plugins: anyio-4.11.0
collected 1 item [0m
test_sequencegen.py [32m.[0m[32m [100%][0m
[32m============================== [32m[1m1 passed[0m[32m in 0.00s[0m[32m ===============================[0mExercises: Add a test_max_iterations() function to test_sequencegen.py that uses pytest.raises to test that
make_sequence()raises aStopIterationwhenconditions=[1,2,3],n_trials=50andmin_dist=5
Then, run !pytest to execute the tests.
Solution
# The function in test_sequencegen.py may look like this:
def test_max_iterations():
with pytest.raises(StopIteration) as stop:
make_sequence(conditions=[1,2,3], n_trials=50, min_dist=5)!pytest test_sequencegen.py::test_max_iterations[1m============================= test session starts ==============================[0m
platform linux -- Python 3.12.12, pytest-8.4.2, pluggy-1.6.0
rootdir: /home/olebi/projects/new-learning-platform/notebooks/psychopy/03_modularization_and_testing/02_automated_testing
plugins: anyio-4.11.0
collected 1 item [0m
test_sequencegen.py [32m.[0m[32m [100%][0m
[32m============================== [32m[1m1 passed[0m[32m in 0.02s[0m[32m ===============================[0mYou can also run Pytest on all of your tests at once, simply run !pytest without any arguments and it will run every module and function that starts with test_
!pytest[1m============================= test session starts ==============================[0m
platform linux -- Python 3.12.12, pytest-8.4.2, pluggy-1.6.0
rootdir: /home/olebi/projects/new-learning-platform/notebooks/psychopy/03_modularization_and_testing/02_automated_testing
plugins: anyio-4.11.0
collected 4 items [0m
test_sequencegen.py [32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [100%][0m
[32m============================== [32m[1m4 passed[0m[32m in 0.02s[0m[32m ===============================[0mAnother nice feature of pytest is that it can determine our test coverage which tells us the percentage of code that is covered by our tests.
A test coverage below 100% means that there are certain parts of your code that are never executed.
This can help us to identify any blind spots in our code. Just run the cell below to let Pytest measure the test coverage for sequencegen.py
!pytest --cov[1m============================= test session starts ==============================[0m
platform linux -- Python 3.12.12, pytest-8.4.2, pluggy-1.6.0
rootdir: /home/olebi/projects/new-learning-platform/notebooks/psychopy/03_modularization_and_testing/02_automated_testing
plugins: cov-7.0.0, anyio-4.11.0
collected 4 items [0m
test_sequencegen.py [32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [100%][0m
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.12.12-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------
sequencegen.py 33 10 70%
test_sequencegen.py 18 0 100%
-----------------------------------------
TOTAL 51 10 80%
[32m============================== [32m[1m4 passed[0m[32m in 0.06s[0m[32m ===============================[0mSection 4: Parameterizing Tests
Writing assert statements to tests all the different combinations of parameters that may be important for you program can be laborious. This is where parameterization is extremely useful. It provides us with an esy interface to run a large number of test. For example, we could test a function like add with a large number of values by parameterizing it with the range() function.
Pytets will take all those values and run our test function with every single one. For this to work, the name in parameterize must have the same name as the parameter of the test function. The example below parameterizes the test_sum function so pytest will run it 1000 times, passing the values from range(1000) to the variable a.
Exercises
%%ipytest
@pytest.mark.parametrize("a", range(1000))
def test_sum(a):
assert sum([a,3]) == a+3 [32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [ 9%]
[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [ 18%]
[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [ 27%]
[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [ 36%]
[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [ 46%]
[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [ 55%]
[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [ 64%]
[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [ 73%]
[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [ 82%]
[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [ 92%]
[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m [100%][0m
[32m[32m[1m1000 passed[0m[32m in 1.53s[0m[0mWe can also parameterize multiple variables. In this case, we pass in both variable names followed by a list of tuples, each of which contains the parameters for one run of the test. In the example below, the parameterized test will run three times, where a will take on the values 1, 3, and 4 and b will take on the values 2, 4 and 6.
%%ipytest
@pytest.mark.parametrize("a, b", [(1,2), (3,4), (5,6)])
def test_sum(a, b):
assert sum([a,b]) == a+b [32m.[0m[32m.[0m[32m.[0m[32m [100%][0m
[32m[32m[1m3 passed[0m[32m in 0.01s[0m[0mExercise: Parameterize the function test_make_sequence_runs_all_iterations() to test that make_sequence raises a StopIteration
- after 10 iterations if
max_iter=10 - after 100 iterations if
max_iter=100 - after 1000 iterations if
max_iter=1000
And run the cell to execute the tests
%%ipytest
from sequencegen import make_sequence
def test_make_sequence_runs_all_iterations(max_iter):
with pytest.raises(StopIteration, match=f"Could not find a sequence after {max_iter} iterations!"):\
make_sequence(conditions=[1,2,3], n_trials=100, min_dist=5)Solution
%%ipytest
from sequencegen import make_sequence
@pytest.mark.parametrize("max_iter", [10, 100, 1000])
def test_make_sequence_runs_all_iterations(max_iter):
with pytest.raises(StopIteration, match=f"Could not find a sequence after {max_iter} iterations!"):
make_sequence(conditions=[1,2,3], n_trials=100, min_dist=5, max_iter=max_iter)[32m.[0m[32m.[0m[32m.[0m[32m [100%][0m
[32m[32m[1m3 passed[0m[32m in 0.04s[0m[0mExercise: Parameterize the test_condition_counts_are_balanced() function below to test that all conditions appear in euqal numbers when:
conditions = [1,2]conditions = [1,2,3]conditions = ["A", "B"]
Then run the cell to execute the tests.
%%ipytest
def test_condition_counts_are_balanced(conditions):
seq = make_sequence(conditions, n_trials=100)
counts = [seq.count(c) for c in conditions]
assert len(set(counts)) == 1Solution
%%ipytest
@pytest.mark.parametrize("conditions", [[1, 2], [1, 2, 3], ['A', 'B']])
def test_condition_counts_are_balanced(conditions):
seq = make_sequence(conditions, n_trials=100)
counts = [seq.count(c) for c in conditions]
assert len(set(counts)) == 1[32m.[0m[32m.[0m[32m.[0m[32m [100%][0m
[32m[32m[1m3 passed[0m[32m in 0.01s[0m[0m%%ipytest
@pytest.mark.parametrize("conditions, min_dist", [
([1,2], 2),
([1,2,3], 3),
([1,2,3,4], 4)
])
def test_impossible_sequence_is_not_made(conditions, min_dist):
with pytest.raises(StopIteration):
make_sequence(conditions, n_trials=100, min_dist=min_dist)[32m.[0m[32m.[0m[32m.[0m[32m [100%][0m
[32m[32m[1m3 passed[0m[32m in 0.09s[0m[0m