0% found this document useful (0 votes)
41 views67 pages

Python Assert Statements in Testing

The document discusses various techniques for writing effective assert statements for unit testing in Python, including: - Adding optional error messages to asserts to provide more context on failures - Using pytest.approx() to compare floats and NumPy arrays containing floats - Writing tests to check for expected exceptions instead of return values using pytest.raises context manager - Testing that expected error messages are raised along with expected exceptions

Uploaded by

BikashGupta
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
41 views67 pages

Python Assert Statements in Testing

The document discusses various techniques for writing effective assert statements for unit testing in Python, including: - Adding optional error messages to asserts to provide more context on failures - Using pytest.approx() to compare floats and NumPy arrays containing floats - Writing tests to check for expected exceptions instead of return values using pytest.raises context manager - Testing that expected error messages are raised along with expected exceptions

Uploaded by

BikashGupta
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

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

You might also like