wolever / parameterized

Parameterized testing with any Python test framework
Other
828 stars 105 forks source link

Does not re-initialize "consumables" in other decorators #160

Closed NickGoog closed 1 year ago

NickGoog commented 1 year ago

Hi, I saw something related in the docs:

when using an iterator or a generator, all the items will be loaded into memory before the start of the test run

https://github.com/wolever/parameterized#exhaustive-usage-examples

Not sure if this deserves a separate mention or a warning in code. Sorry if this is a known issue, and I couldn't find it.

The Problem: “Consumables” (e.g. iterators) declared in non-parameterized decorators that are used by one test case remain consumed for other test cases.

Reproduction:

from parameterized import parameterized
from unittest import mock, TestCase

class MyIterator:
  def get_iterator(self):
    raise NotImplementedError

class TestConsumableInOtherDecorator(TestCase):
  @parameterized.expand(
     [('first run',), ('second run',)]
  )
  @mock.patch.object(MyIterator, 'get_iterator', new=mock.Mock(return_value=iter([1])))
  def test_the_problem(self, case):
    print(case)
    assert list(MyIterator.get_iterator()) == [1]

  @parameterized.expand(
     [('first run',), ('second run',)]
  )
  @mock.patch.object(MyIterator, 'get_iterator', new=mock.Mock(return_value=[1]))
  def test_isolating_the_problem(self, case):
    print(case)
    assert list(MyIterator.get_iterator()) == [1]

  @parameterized.expand(
     [('first run',), ('second run',)]
  )
  @mock.patch.object(MyIterator, 'get_iterator')
  def test_the_workaround(self, case, mock_get_iterator):
    mock_get_iterator.return_value = iter([1])
    print(case)
    assert list(MyIterator.get_iterator()) == [1]

Output:

$ python -m unittest -v test.py
test_isolating_the_problem_0_first_run (test.TestConsumableInOtherDecorator) ... first run
ok
test_isolating_the_problem_1_second_run (test.TestConsumableInOtherDecorator) ... second run
ok
test_the_problem_0_first_run (test.TestConsumableInOtherDecorator) ... first run
ok
test_the_problem_1_second_run (test.TestConsumableInOtherDecorator) ... second run
FAIL
test_the_workaround_0_first_run (test.TestConsumableInOtherDecorator) ... first run
ok
test_the_workaround_1_second_run (test.TestConsumableInOtherDecorator) ... second run
ok

======================================================================
FAIL: test_the_problem_1_second_run (test.TestConsumableInOtherDecorator)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/google/home/hartunian/Desktop/lib/python3.10/site-packages/parameterized/parameterized.py", line 533, in standalone_func
    return func(*(a + p.args), **p.kwargs)
  File "/usr/lib/python3.10/unittest/mock.py", line 1379, in patched
    return func(*newargs, **newkeywargs)
  File "/usr/local/google/home/hartunian/Desktop/lib/python3.10/site-packages/parameterized/parameterized.py", line 81, in dummy_func
    return orgfunc(*args, **kwargs)
  File "/usr/lib/python3.10/unittest/mock.py", line 1379, in patched
    return func(*newargs, **newkeywargs)
  File "/usr/local/google/home/hartunian/Desktop/test.py", line 17, in test_the_problem
    assert list(MyIterator.get_iterator()) == [1]
AssertionError

We're fine with the workaround but just wanted to raise awareness.

Thanks!

wolever commented 1 year ago

Hey! Thanks for raising this. Yes, it would be nice to mention this in the documentation, possibly with an example of how Mock.side_effect can be used to work around it: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.side_effect

A pull request with updates to the documentation would be very welcome!

NickGoog commented 1 year ago

@wolever , made the PR! https://github.com/wolever/parameterized/pull/164