I don't quite understand what Python's branch coverage stats are trying to tell me. Given code of the form
def f(a, b):
c = (i for i in a)
d = (j for j in b) # Line of interest
return dict(zip(c, d))
print(f(['a', 'b'], [1, 2]))
which is imported during unit testing, Python's standard branch coverage tells me that the # Line of interest
line is only partially covered (n->-n
on the CLI output, "n ↛ exit [?]" in the pretty html report).
The returned dict is clearly printed out, and executing with empty lists still yields the uncovered line.
Am I misinterpreting the coverage output? Does this smell like a bug?
Python 3.5.1, Coverage 4.0.3
I've investigated this further, and I don't think it is a bug in coverage. When the first generator (c
) terminates, zip()
efficiently doesn't bother collecting any more values from the second generator (d
), so branch coverage doesn't track d
as run to completion, even though every element is actually extracted.
If you instead write:
def f(a, b):
c = (i for i in a)
d = tuple(j for j in b) # Line of interest
return dict(zip(c, d))
print(f(['a', 'b'], [1, 2]))
as one would expect the second generator is run to completion and coverage is happy, even though the output is identical.
I don't think there's a simple way around this, even if you write the generator expression out as a generator function containing the same for loop, you get a (slightly clearer) error that execution never jumped to function exit.
I think this is just a limitation of coverage and the exit conditions of generators, as it can't know whether a generator is supposed to exit, so it flags the uncovered case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With