'Testing a Generator in Python
I programmed a very simple generator in Python 3.4:
class Gen:
def __init__(self, xml_lines, attribs):
#...
def _get_values(self, xml_line):
# get only specific "attribs" from a line in a "xml"
def values(self):
for line in self.xml_lines:
yield self._get_values(line)
The code works when I use a for loop to consume the generated values:
reader = Gen(...):
for v in reader.values():
print(v)
But I'm now trying to create a unit test, and for that, I'd need to get each value at a time (outside a loop). I'm trying something like this:
import unittest
#...
reader = Gen(...):
v = reader.values()
self.assertIsNone(v)
When I try that, I always get a
AssertionError: <generator object next_values at 0x7fe2a5f9e3f0> is not None
So, when I call the values myself, it return something (is it a pointer?) instead of the generated value.
As I'm considering I'm using a basic generator pattern, my question is a little broader then my own code: what's the proper way to test a Python Generator?
Solution 1:[1]
A generator is meant to be iterated through. You can use the next() function to get the next value of the generator. Be aware that if your generator has exhausted it's values, it will raise a StopIteration
exception.
reader = Gen(...):
values = reader.values()
v = next(values)
self.assertIsNone(v)
Solution 2:[2]
I needed to unit-test a generator myself, so I created this helper function:
def generator_tester(generator_iterator_to_test, expected_values):
range_index = 0
for actual in generator_iterator_to_test:
assert range_index + 1 <= len(expected_values), 'Too many values returned from range'
assert expected_values[range_index] == actual
range_index += 1
assert range_index == len(expected_values), 'Too few values returned from range'
Here's an example of using it:
generator_tester(
Gen(...),
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
)
Solution 3:[3]
I needed to check that the value produced by the return
in the generator was valid, and I needed to know how the yielded values differed from the expected ones when the lenght was different. I ended up using a pretty big helper function, which reads up to 6 values that are produced but not expected, or the other way around, and displays them. Here it is:
from unittest import TestCase
class MISSING:
pass
def _generator_tester(
self,
iterator,
expected_value_list,
expected_returned_value=MISSING,
):
"""
Helper method to test a generator.
Args:
iterator (Iterator):
generator iterator to test
expected_value_list (list):
list of the values the generator should produce
expected_returned_value (any):
the final values returned by the generator
"""
expected_value_iterator = iter(expected_value_list)
try:
for expected in expected_value_iterator:
actual = next(iterator)
self.assertEqual(actual, expected)
except StopIteration:
# Generator iterator is too short.
#
# len(iterator) < len(expected_value_iterator)
#
# Let's see what values were expected
remaining_expected_values = [expected]
for value in iterator:
remaining_expected_values.append(value)
if len(remaining_expected_values) == 8:
remaining_expected_values[-1] = Ellipsis
break
self.fail(f"Too few values. Expected: {remaining_expected_values}")
try:
remaining_values = [next(iterator)]
except StopIteration as stop_iteration:
# Generator iterator is the right length
#
# len(iterator) == len(expected_value_iterator)
#
# Let's check the return value of the iterator
if expected_returned_value != MISSING:
self.assertEqual(stop_iteration.value, expected_returned_value)
else:
# Generator iterator is too long
#
# len(iterator) > len(expected_value_iterator)
#
# Let's see what are the extra values of the generator
for value in iterator:
remaining_values.append(value)
if len(remaining_values) == 8:
remaining_values[-1] = Ellipsis
break
self.fail(f"Too many values: {remaining_values}")
Solution 4:[4]
Given that you can only test a finite number of outputs from a generator, you may just turn the iterator to a list by:
self.assertEqual(list(your_generator_function_call(your_inputs)), list_of_exepcted_outputs)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Nigini |
Solution 2 | |
Solution 3 | |
Solution 4 | Gary |