Mastering assert
statements
U N I T T E S T I N G F O R D ATA S C I E N C E I N P Y T H O N
Dibya Chakravorty
Test Automation Engineer
Theoretical structure of an assertion
assert boolean_expression
UNIT TESTING FOR DATA SCIENCE IN PYTHON
The optional message argument
assert boolean_expression, message
assert 1 == 2, "One is not equal to two!"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: One is not equal to two!
assert 1 == 1, "This will not be printed since assertion passes"
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Adding a message to a unit test
test module: test_row_to_list.py
import pytest
...
def test_for_missing_area():
assert row_to_list("\t293,410\n") is None
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Adding a message to a unit test
test module: test_row_to_list.py test module: test_row_to_list.py
import pytest import pytest
... ...
def test_for_missing_area(): def test_for_missing_area_with_message():
assert row_to_list("\t293,410\n") is None actual = row_to_list("\t293,410\n")
expected = None
message = ("row_to_list('\t293,410\n') "
"returned {0} instead "
"of {1}".format(actual, expected)
)
assert actual is expected, message
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Test result report with message
test_on_missing_area() output on failure
E AssertionError: assert ['', '293,410'] is None
E + where ['', '293,410'] = row_to_list('\t293,410\n')
test_on_missing_area_with_message() output on failure
> assert actual is expected, message
E AssertionError: row_to_list('\t293,410\n') returned ['', '293,410'] instead
of None
E assert ['', '293,410'] is None
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Recommendations
Include a message with assert statements.
Print values of any variable that is relevant to debugging.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Beware of oat return values!
0.1 + 0.1 + 0.1 == 0.3
False
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Beware of oat return values!
0.1 + 0.1 + 0.1
0.30000000000000004
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Don't do this
assert 0.1 + 0.1 + 0.1 == 0.3, "Usual way to compare does not always work with floats!"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: Usual way to compare does not always work with floats!
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Do this
Use [Link]() to wrap expected return value.
assert 0.1 + 0.1 + 0.1 == [Link](0.3)
UNIT TESTING FOR DATA SCIENCE IN PYTHON
NumPy arrays containing oats
assert [Link]([0.1 + 0.1, 0.1 + 0.1 + 0.1]) == [Link]([Link]([0.2, 0.3]))
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Multiple assertions in one unit test
convert_to_int("2,081")
2081
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Multiple assertions in one unit test
test module: test_convert_to_int.py test_module: test_convert_to_int.py
import pytest import pytest
... ...
def test_on_string_with_one_comma(): def test_on_string_with_one_comma():
assert convert_to_int("2,081") == 2081 return_value = convert_to_int("2,081")
assert isinstance(return_value, int)
assert return_value == 2081
Test will pass only if both assertions pass.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Let's practice writing
assert statements!
U N I T T E S T I N G F O R D ATA S C I E N C E I N P Y T H O N
Testing for
exceptions instead of
return values
U N I T T E S T I N G F O R D ATA S C I E N C E I N P Y T H O N
Dibya Chakravorty
Test Automation Engineer
Example
import numpy as np
example_argument = [Link]([[2081, 314942],
[1059, 186606],
[1148, 206186],
]
)
split_into_training_and_testing_sets(example_argument)
(array([[1148, 206186],
[2081, 314942],
]
),
array([[1059, 186606]])
)
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Example
import numpy as np
example_argument = [Link]([[2081, 314942], # must be two dimensional
[1059, 186606],
[1148, 206186],
]
)
split_into_training_and_testing_sets(example_argument)
(array([[1148, 206186],
[2081, 314942],
]
),
array([[1059, 186606]])
)
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Example
import numpy as np
example_argument = [Link]([2081, 314942, 1059, 186606, 1148, 206186]) # one dimensional
split_into_training_and_testing_sets(example_argument)
ValueError: Argument data array must be two dimensional. Got 1 dimensional array instead!
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Unit testing exceptions
Goal
Test if split_into_training_and_testing_set() raises ValueError with one dimensional
argument.
def test_valueerror_on_one_dimensional_argument():
example_argument = [Link]([2081, 314942, 1059, 186606, 1148, 206186])
with [Link](ValueError):
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Theoretical structure of a with statement
with ____:
print("This is part of the context") # any code inside is the context
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Theoretical structure of a with statement
with context_manager:
print("This is part of the context") # any code inside is the context
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Theoretical structure of a with statement
with context_manager:
# <--- Runs code on entering context
print("This is part of the context") # any code inside is the context
# <--- Runs code on exiting context
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Theoretical structure of a with statement
with [Link](ValueError):
# <--- Does nothing on entering the context
print("This is part of the context")
# <--- If context raised ValueError, silence it.
# <--- If the context did not raise ValueError, raise an exception.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Theoretical structure of a with statement
with [Link](ValueError):
raise ValueError # context exits with ValueError
# <--- [Link](ValueError) silences it
with [Link](ValueError):
pass # context exits without raising a ValueError
# <--- [Link](ValueError) raises Failed
Failed: DID NOT RAISE <class 'ValueError'>
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Unit testing exceptions
def test_valueerror_on_one_dimensional_argument():
example_argument = [Link]([2081, 314942, 1059, 186606, 1148, 206186])
with [Link](ValueError):
split_into_training_and_testing_sets(example_argument)
If function raises expected ValueError , test will pass.
If function is buggy and does not raise ValueError , test will fail.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Testing the error message
ValueError: Argument data array must be two dimensional. Got 1 dimensional array instead!
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Testing the error message
def test_valueerror_on_one_dimensional_argument():
example_argument = [Link]([2081, 314942, 1059, 186606, 1148, 206186])
with [Link](ValueError) as exception_info: # store the exception
split_into_training_and_testing_sets(example_argument)
# Check if ValueError contains correct message
assert exception_info.match("Argument data array must be two dimensional. "
"Got 1 dimensional array instead!"
)
exception_info stores the ValueError .
exception_info.match(expected_msg) checks if expected_msg is present in the actual
error message.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Let's practice unit
testing exceptions.
U N I T T E S T I N G F O R D ATA S C I E N C E I N P Y T H O N
The well tested
function
U N I T T E S T I N G F O R D ATA S C I E N C E I N P Y T H O N
Dibya Chakravorty
Test Automation Engineer
Example
import numpy as np
example_argument_value = [Link]([[2081, 314942],
[1059, 186606],
[1148, 206186],
]
)
split_into_training_and_testing_sets(example_argument_value)
(array([[1148, 206186], # Training array
[2081, 314942],
]
),
array([[1059, 186606]]) # Testing array
)
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Test for length, not value
import numpy as np
example_argument_value = [Link]([[2081, 314942],
[1059, 186606],
[1148, 206186],
]
)
split_into_training_and_testing_sets(example_argument_value)
(array([[1148, 206186], # Training array has int(0.75 * example_argument_value.shape[0]) rows
[2081, 314942],
]
),
array([[1059, 186606]]) # Rest of the rows go to the testing array
)
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Test arguments and expected return values
Number of rows (argument) Number of rows (training array) Number of rows (testing array)
8 int(0.75 * 8) = 6 8 - int(0.75 * 8) = 2
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Test arguments and expected return values
Number of rows (argument) Number of rows (training array) Number of rows (testing array)
8 int(0.75 * 8) = 6 8 - int(0.75 * 8) = 2
10 int(0.75 * 10) = 7 10 - int(0.75 * 10) = 3
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Test arguments and expected return values
Number of rows (argument) Number of rows (training array) Number of rows (testing array)
8 int(0.75 * 8) = 6 8 - int(0.75 * 8) = 2
10 int(0.75 * 10) = 7 10 - int(0.75 * 10) = 3
23 int(0.75 * 23) = 17 23 - int(0.75 * 23) = 6
UNIT TESTING FOR DATA SCIENCE IN PYTHON
How many arguments to test?
Input array number of rows Training array number of rows Testing array number of rows
8 int(0.75 * 8) = 6 8 - int(0.75 * 8) = 2
10 int(0.75 * 10) = 7 10 - int(0.75 * 10) = 3
23 int(0.75 * 23) = 17 23 - int(0.75 * 23) = 6
... ... ...
... ... ...
... ... ...
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Test argument types
Test for these argument types
Bad arguments.
Special arguments.
Normal arguments.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Test argument types
Test for these argument types
Bad arguments. ✓
Special arguments.
Normal arguments.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Test argument types
Test for these argument types
Bad arguments. ✓
Special arguments. ✓
Normal arguments.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Test argument types
Test for these argument types
Bad arguments. ✓
Special arguments. ✓
Normal arguments. ✓
UNIT TESTING FOR DATA SCIENCE IN PYTHON
The well tested function
Test for these argument types
Bad arguments. ✓
Special arguments. ✓
Normal arguments. ✓
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Type I: Bad arguments
When passed bad arguments, function raises an exception.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Type I: Bad arguments (one dimensional array)
When passed bad arguments, function raises an exception.
Argument Type Num rows (training) Num rows (testing) exceptions
One dimensional Bad - - ValueError
Example: [Link]([845.0, 31036.0, 1291.0,72205.0])
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Type I: Bad arguments (array with only one row)
When passed bad arguments, function raises an exception.
Argument Type Num rows (training) Num rows (testing) exceptions
One dimensional Bad - - ValueError
Contains 1 row Bad - - ValueError
Example: [Link]([[845.0, 31036.0]])
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Type II: Special arguments
Boundary values.
For some argument values, function uses special logic.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Boundary values
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Boundary values
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Test arguments table
Argument Type Num rows (training) Num rows (testing) exceptions
One dimensional Bad - - ValueError
Contains 1 row Bad - - ValueError
Contains 2 rows Special int(0.75 * 2) = 1 2 - int(0.75 * 2) = 1 -
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Arguments triggering special logic
Argument Type Num rows (training) Num rows (testing) exceptions
One dimensional Bad - - ValueError
Contains 1 row Bad - - ValueError
Contains 2 rows Special int(0.75 * 2) = 1 2 - int(0.75 * 2) = 1 -
Contains 4 rows int(0.75 * 4) = 3 4- int(0.75 * 4) = 1 -
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Arguments triggering special logic
Argument Type Num rows (training) Num rows (testing) exceptions
One dimensional Bad - - ValueError
Contains 1 row Bad - - ValueError
Contains 2 rows Special int(0.75 * 2) = 1 2 - int(0.75 * 2) = 1 -
Contains 4 rows Special 3 2 12 -
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Normal arguments
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Argument Type Num rows (training) Num rows (testing) exceptions
One dimensional Bad - - ValueError
Contains 1 row Bad - - ValueError
Contains 2 rows Special int(0.75 * 2) = 1 2 - int(0.75 * 2) = 1 -
Contains 3 rows Special int(0.75 * 3) = 2 3 - int(0.75 * 3) = 1 -
Contains 4 rows Special 32 12 -
Contains 5 rows Special int(0.75 * 5) = 3 5 - int(0.75 * 5) = 2 -
Contains 6 rows Normal int(0.75 * 6) = 4 6 - int(0.75 * 6) = 2 -
Contains 8 rows Normal int(0.75 * 8) = 6 8 - int(0.75 * 6) = 2 -
UNIT TESTING FOR DATA SCIENCE IN PYTHON
split_into_training_and_testing_sets()
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Caveat
Not all functions have bad or special arguments.
In this case, simply ignore these class of arguments.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Let's apply this to
other functions!
U N I T T E S T I N G F O R D ATA S C I E N C E I N P Y T H O N
Test Driven
Development (TDD)
U N I T T E S T I N G F O R D ATA S C I E N C E I N P Y T H O N
Dibya Chakravorty
Test Automation Engineer
Writing unit tests is often skipped
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Usual priorities in the industry
1. Feature development.
2. Unit testing.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Unit tests never get written
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Test Driven Development (TDD)
Implementation
Test
FAIL PASS
Accepted
Bugfix
implementation
Feature request
Bug found
or Refactoring
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Test Driven Development (TDD)
Write unit tests Implementation
Test
FAIL PASS
Accepted
Bugfix
implementation
Feature request
Bug found
or Refactoring
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Write unit tests before implementation!
Unit tests cannot be deprioritized.
Time for writing unit tests factored in
implementation time.
Requirements are clearer and implementation
easier.
UNIT TESTING FOR DATA SCIENCE IN PYTHON
In the coding exercises...
We will use TDD to develop convert_to_int() .
convert_to_int("2,081")
2081
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Step 1: Write unit tests and x requirements
Test module: test_convert_to_int.py
import pytest
def test_with_no_comma():
...
def test_with_one_comma():
...
def test_with_two_commas():
...
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Step 2: Run tests and watch it fail
!pytest test_convert_to_int.py
============================= test session starts ==============================
platform linux -- Python 3.6.7, pytest-4.0.1, py-1.8.0, pluggy-0.11.0
rootdir: /tmp/tmpbhadho_b, inifile:
plugins: mock-1.10.0
collecting ...
collected 6 items
test_convert_to_int.py FFFFFF [100%]
=========================== 6 failed in 0.06 seconds ===========================
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Step 3: Implement function and run tests again
def convert_to_int():
...
!pytest test_convert_to_int.py
============================= test session starts ==============================
platform linux -- Python 3.6.7, pytest-4.0.1, py-1.8.0, pluggy-0.11.0
rootdir: /tmp/tmp793ds6mt, inifile:
plugins: mock-1.10.0
collecting ...
collected 6 items
test_convert_to_int.py ...... [100%]
=========================== 6 passed in 0.03 seconds ===========================
UNIT TESTING FOR DATA SCIENCE IN PYTHON
Let's apply TDD!
U N I T T E S T I N G F O R D ATA S C I E N C E I N P Y T H O N