This isn't the API that you deal with when you write tests, but rather an API that unittest itself uses when running tests. You could think of it as two interfaces: one for test frameworks and one for test authors. Both APIs are real, but both are poorly documented and often misunderstood or abused.
TestCaseAn instance of
TestCase represents a single test. What you think of as a single test is up to you, but most of the time it's a unit test.A
TestCase object must provide the following methods.This first list of methods can be thought of as a single interface, which these blog posts will call
ITest given the lack of any better name.countTestCases()- A method that returns the number of test cases this represents. It should always return 1.
run(result=None)- Calling this method actually runs the test.
resultis aTestResultobject.runmust callresult.startTest(self)when it commences running the test andresult.stopTest(self)when it is finished. Between these calls it must call a method onresultto signal the result of the test.runmust never raise an exception, and its return value is ignored. Ifresultis not provided, theTestCaseis obliged to make one. __call__(result)- Identical to
run(result), provided for backwards compatibility. debug()- Calling this method runs the test without collecting its results. It may raise exceptions. This method is rarely called by test frameworks.
The following methods are specific to individual test case objects. We call this interface
ITestCase.id()- Should return a string that uniquely identifies the test. For Python tests, the fully-qualified Python name works well. The uniqueness of the id is not enforced.
shortDescription()- Should return a string that describes the test. Many test frameworks use this value to display test results.
__str__- Should return a string that describes the test. Frequently the same as either
shortDescription()orid(). Many test frameworks use this value to display test results.
TestCase. We'll deal with that in a later post.TestSuiteA
TestSuite represents nothing more or less than a bunch of tests.A
TestSuite must provide the ITest interface described above, with the differences that you would expect from something that represents many tests: countTestCases returns the number of tests in the suite; run runs many tests and thus calls result.startTest and kin many times over; debug is the same and can explode anywhere.One difference is that
TestSuite.run must stop running tests as soon as it detects that result.shouldStop is true.In addition,
TestSuite implements the following interface, which I'm giving the completely arbitrary non-existent name of ITestSuite.addTest(test)- Takes an
ITestand adds it to the suite. addTests(tests)- Takes an iterable of
ITests and adds them to the suite. Normally equivalent to[suite.addTest(test) for test in tests]. __iter__- All test suites must be iterable. Iterating over a test suite yields
ITests. These may differ from theITests provided toaddTestandaddTests.
TestResult, the subclassing interface of TestCase and tell you exactly what I think about test loaders, test runners and the like.I'm blogging this partly because I don't know where else to write this up, but mostly because I need your help to make sure that I'm being clear and correct. Please comment with questions and corrections, and let me know if you find this at all helpful.

6 comments:
Heck yeah, it's useful!
Please keep it up!
I think it is quite useful.
We've certainly had our share of unittest issues in bzr, so it is nice to have a fairly easy-to-read explanation of how it is at least supposed to work.
Great stuff Jonathan. If you want to formulate these as patches to the unittest docs then please feel free. :-)
Would be interested in your feedback on my unittest plugin proposal too.
Thanks for the comments all. I'm glad you like it.
Michael, once I'm finished with the series and have a chance to get some more feedback – particularly from other framework authors – I'll try to turn these into patches. (And also try to get over the fact that I won't be able to use zope.interface to do so).
Your plugin proposal is up there on my list of things to respond to, but I likely won't get a chance until the weekend.
I don't understand how a TestCase can be a single test when the test suites I use all have multiple test cases per TestCase.
Aaron, an instance of TestCase is a single test.
When you have many test_ methods on a TestCase, what actually happens is that one TestCase object gets constructed for each test method.
Say you've got:
class TestFoo(TestCase):
def test_a(self):
pass
def test_b(self):
pass
(please forgive the lack of indentation.)
Then the default test loader will construct something like:
[TestFoo("test_a"), TestFoo("test_b")]
Thanks for asking the question. It's a common point of confusion. Indeed, it tripped me up the first year or so I was writing Trial.
Post a Comment