Batu Lab NotesPractical developer guides

Test generator failures at iteration time instead of construction

By Batu ยท English technical notes

Also published in our primary archive.

Test generator failures at iteration time instead of construction

Test a generator failure by advancing the generator inside assertRaises, not by calling the generator function. Calling fails_before_yield() below creates a generator object successfully; its ValueError occurs only on the first next(). The contrasting generator yields "ready" once, then raises on its second advancement. The printed boundaries make both timings explicit.

A generator function does not execute its body merely because it is called. Its generator iterator resumes when an iteration method advances it, and the Python generator-iterator reference explains that next() resumes execution until the next yield or termination. Therefore, an assertion around construction would miss a body exception in this situation. Create the iterator first, then put the precise advance expected to fail inside the assertion context.

This distinction helps make test intent readable. A first-advance failure can represent validation deferred by a lazy API; a later failure can represent a bad item encountered after usable output. The example checks exception messages as well as yielded values, so a change in either boundary fails deterministically. It does not assert anything about generator cleanup, send(), throw(), or resource release; those require separate cases if the API uses them. Basic generator behavior used here has no newer-version requirement.

AI assistance disclosure: this article was drafted with AI assistance and runs only synthetic generator code.

import unittest

def fails_before_yield():
    raise ValueError("missing header")
    yield "unreachable"

def yields_then_fails():
    yield "ready"
    raise ValueError("bad trailer")

case = unittest.TestCase()
first_boundary = fails_before_yield()
assert iter(first_boundary) is first_boundary
with case.assertRaises(ValueError) as first_error:
    next(first_boundary)
assert str(first_error.exception) == "missing header"

later_boundary = yields_then_fails()
assert next(later_boundary) == "ready"
with case.assertRaises(ValueError) as later_error:
    next(later_boundary)
assert str(later_error.exception) == "bad trailer"

print("construction: no exception")
print(f"first next: {first_error.exception}")
print(f"second next after ready: {later_error.exception}")
construction: no exception
first next: missing header
second next after ready: bad trailer