Yelp / Testify

A more pythonic testing framework.
Other
308 stars 67 forks source link

Troscoe 35978 exceptions in class setup teardown http reporter #116

Closed mrtyler closed 11 years ago

mrtyler commented 12 years ago

Changes:

One of this branch's many virtues is that it addresses issue #84.

mrtyler commented 11 years ago

This is now complete (...hopefully). Additional changes:

baris commented 11 years ago

Ship it! :)

blampe commented 11 years ago

Cool! Big fan of the test coverage. I'm not super familiar with this part of Testify so I don't have much to comment on. My one question would be what our behavior is like when class_setup_teardown fails (before and after the yield).

mrtyler commented 11 years ago

Re: better solutions than the misappropriation of TestResult: please add your ideas to issue #121.

mrtyler commented 11 years ago

Re: class_setup_teardown

Consider this test case:

class FakeClassSetupTeardownSetupExceptionTestCase(TestCase):
    @class_setup_teardown
    def class_setup_teardown_raises_exception_during_setup(self):
        raise FakeClassFixtureException
        yield # Never reached obv

    def test1(self):
        print "i am test1"
        pass

    def test2(self):
        print "i am test2"
        pass

Here is what current stable Testify 0.3.4 does:

$ testify -v yelp.tests.tmp_blowup.FakeClassSetupTeardownSetupExceptionTestCase
Traceback (most recent call last):
  File "/usr/bin/testify", line 25, in <module>
    test_program.TestProgram()
  File "/usr/lib/python2.6/dist-packages/testify/test_program.py", line 276, in __init__
    result = runner.run()
  File "/usr/lib/python2.6/dist-packages/testify/test_runner.py", line 157, in run
    runnable()
  File "/usr/lib/python2.6/dist-packages/testify/plugins/profile.py", line 24, in run_test_case
    return runnable()
  File "/usr/lib/python2.6/dist-packages/testify/plugins/code_coverage.py", line 25, in run_test_case
    return runnable()
  File "/usr/lib/python2.6/dist-packages/testify/plugins/seed.py", line 24, in run_test_case
    return runnable()
  File "/usr/lib/python2.6/dist-packages/testify/test_case.py", line 295, in run
    self.__enter_context_managers(self.class_setup_teardown_fixtures, self.__run_test_methods)
  File "/usr/lib/python2.6/dist-packages/testify/test_case.py", line 362, in __enter_context_managers
    with contextmanager(fixture_methods[0])():
  File "/usr/lib/python2.6/contextlib.py", line 16, in __enter__
    return self.gen.next()
  File "./yelp/tests/tmp_blowup.py", line 64, in class_setup_teardown_raises_exception_during_setup
    raise FakeClassFixtureException
yelp.tests.tmp_blowup.FakeClassFixtureException

Note that this isn't a report of an exception in a test; Testify fails to catch FakeClassFixtureException and dies!

Here is what my branch does:

$ PYTHONPATH=~/Testify/ ~/Testify/bin/testify -v yelp.tests.tmp_blowup.FakeClassSetupTeardownSetupExceptionTest
Case
yelp.tests.tmp_blowup FakeClassSetupTeardownSetupExceptionTestCase.test1 ... i am test1
ok in 0.00s
yelp.tests.tmp_blowup FakeClassSetupTeardownSetupExceptionTestCase.test2 ... i am test2
ok in 0.00s

PASSED.  2 tests / 1 case: 2 passed, 0 failed.  (Total test time 0.00s)

So... that's a bug :(. I'll take a crack at that shortly.

Now consider this test case:

class FakeClassSetupTeardownTeardownExceptionTestCase(TestCase):
    @class_setup_teardown
    def class_setup_teardown_raises_exception_during_teardown(self):
        yield
        raise FakeClassFixtureException

    def test1(self):
        print "i am test1"
        pass

    def test2(self):
        print "i am test2"
        pass

With Testify 0.3.4:

$ testify -v yelp.tests.tmp_blowup.FakeClassSetupTeardownTeardownExceptionTestCase
yelp.tests.tmp_blowup FakeClassSetupTeardownTeardownExceptionTestCase.test1 ... i am test1
ok in 0.00s
yelp.tests.tmp_blowup FakeClassSetupTeardownTeardownExceptionTestCase.test2 ... i am test2
ok in 0.00s
Traceback (most recent call last):
  File "/usr/bin/testify", line 25, in <module>
    test_program.TestProgram()
  File "/usr/lib/python2.6/dist-packages/testify/test_program.py", line 276, in __init__
    result = runner.run()
  File "/usr/lib/python2.6/dist-packages/testify/test_runner.py", line 157, in run
    runnable()
  File "/usr/lib/python2.6/dist-packages/testify/plugins/profile.py", line 24, in run_test_case
    return runnable()
  File "/usr/lib/python2.6/dist-packages/testify/plugins/code_coverage.py", line 25, in run_test_case
    return runnable()
  File "/usr/lib/python2.6/dist-packages/testify/plugins/seed.py", line 24, in run_test_case
    return runnable()
  File "/usr/lib/python2.6/dist-packages/testify/test_case.py", line 295, in run
    self.__enter_context_managers(self.class_setup_teardown_fixtures, self.__run_test_methods)
  File "/usr/lib/python2.6/dist-packages/testify/test_case.py", line 363, in __enter_context_managers
    self.__enter_context_managers(fixture_methods[1:], callback)
  File "/usr/lib/python2.6/contextlib.py", line 23, in __exit__
    self.gen.next()
  File "./yelp/tests/tmp_blowup.py", line 79, in class_setup_teardown_raises_exception_during_teardown
    raise FakeClassFixtureException
yelp.tests.tmp_blowup.FakeClassFixtureException

Again, Testify crashes.

With my branch:

troscoe@dev7:~/pg/loc (troscoe_class_teardown) $ PYTHONPATH=~/Testify/ ~/Testify/bin/testify -v yelp.tests.tmp_blowup.FakeClassSetupTeardownTeardownExceptionTestCase
yelp.tests.tmp_blowup FakeClassSetupTeardownTeardownExceptionTestCase.test1 ... i am test1
ok in 0.00s
yelp.tests.tmp_blowup FakeClassSetupTeardownTeardownExceptionTestCase.test2 ... i am test2
ok in 0.00s
yelp.tests.tmp_blowup FakeClassSetupTeardownTeardownExceptionTestCase.class_setup_teardown_raises_exception_during_teardown ... error: yelp.tests.tmp_blowup FakeClassSetupTeardownTeardownExceptionTestCase.class_setup_teardown_raises_exception_during_teardown
Traceback (most recent call last):
  File "/usr/lib/python2.6/contextlib.py", line 23, in __exit__
    self.gen.next()
  File "./yelp/tests/tmp_blowup.py", line 79, in class_setup_teardown_raises_exception_during_teardown
    raise FakeClassFixtureException
FakeClassFixtureException

ERROR in 0.00s

FAILED.  3 tests / 1 case: 2 passed, 1 failed.  (Total test time 0.00s)

This result is correct!

bukzor commented 11 years ago

I was discussing possible solutions for this issue with Ken, before we knew of this pull request. At first we considered implementing somethign like what you've done here, inserting a fake test to which we can attach the teardown failure, but making the total number of tests vary based on how things fail seems wrong. What Ken suggested, which seems better, is to say that the associated tests failed, since they failed to tear down. This means the number of tests doesn't vary, just the number of failures.

Is that harder to do than what you've done here? It seems like it would factor out the need for the special test method named "run".

In any case, @class_setup / @setup / @teardown / @class_teardown should all have the same behavior with regards to error reporting. I see a special-case for "class_teardown" which makes me concerned. What was your rationale there?

mrtyler commented 11 years ago

@bukzor (and Ken),

Testify reports results as they happen. By the time we discover that a class_teardown method raised an exception, the results of the test methods in that test case have already been reported. Going back and amending these results would require significant refactoring and would be impossible for something like the basic TextLogger (we already printed; there's no going back).

We could have Testify wait until an entire test case has run before reporting, but that would require significant re-design and I'm not sure it's desirable anyway.

Since the test methods from the test case have already been reported, the only thing left to do is report a new test result to communicate the class_teardown failure.

setup, teardown, and class_setup have the luxury of running before any test methods have run (or finished) and thus before they have been reported. These fixtures use the strategy you and Ken specify -- report the fixture error as the result of running each test method.

mrtyler commented 11 years ago

@blampe et al,

I added tests for the class_setup_teardown behavior and fixed some stuff. Compare and contrast:

class_setup:

$ PYTHONPATH=~/Testify/ ~/Testify/bin/testify -v yelp.tests.tmp_blowup FakeClassSetupTestCase
yelp.tests.tmp_blowup FakeClassSetupTestCase.test1 ... error: yelp.tests.tmp_blowup FakeClassSetupTestCase.test1
Traceback (most recent call last):
  File "./yelp/tests/tmp_blowup.py", line 51, in class_setup_raises_exception
    raise FakeClassFixtureException
FakeClassFixtureException

ERROR in 0.00s
yelp.tests.tmp_blowup FakeClassSetupTestCase.test2 ... error: yelp.tests.tmp_blowup FakeClassSetupTestCase.test2
Traceback (most recent call last):
  File "./yelp/tests/tmp_blowup.py", line 51, in class_setup_raises_exception
    raise FakeClassFixtureException
FakeClassFixtureException

ERROR in 0.00s

FAILED.  2 tests / 1 case: 0 passed, 2 failed.  (Total test time 0.00s)

Setup phase of class_setup_teardown:

$ PYTHONPATH=~/Testify/ ~/Testify/bin/testify -v yelp.tests.tmp_blowup FakeClassSetupTeardownSetupExceptionTestCase
yelp.tests.tmp_blowup FakeClassSetupTeardownSetupExceptionTestCase.test1 ... error: yelp.tests.tmp_blowup FakeClassSetupTeardownSetupExceptionTestCase.test1
Traceback (most recent call last):
  File "/usr/lib/python2.6/contextlib.py", line 16, in __enter__
    return self.gen.next()
  File "./yelp/tests/tmp_blowup.py", line 64, in class_setup_teardown_raises_exception_during_setup
    raise FakeClassFixtureException
FakeClassFixtureException

ERROR in 0.00s
yelp.tests.tmp_blowup FakeClassSetupTeardownSetupExceptionTestCase.test2 ... error: yelp.tests.tmp_blowup FakeClassSetupTeardownSetupExceptionTestCase.test2
Traceback (most recent call last):
  File "/usr/lib/python2.6/contextlib.py", line 16, in __enter__
    return self.gen.next()
  File "./yelp/tests/tmp_blowup.py", line 64, in class_setup_teardown_raises_exception_during_setup
    raise FakeClassFixtureException
FakeClassFixtureException

ERROR in 0.00s

FAILED.  2 tests / 1 case: 0 passed, 2 failed.  (Total test time 0.00s)
bukzor commented 11 years ago

@mrtyler: That makes lots of sense, thanks for the reply. The behavior is certainly improved, and that's the main thing that matters.

blampe commented 11 years ago

LGTM!