diff options
author | Martin Pool <mbp@sourcefrog.net> | 2009-03-09 16:45:33 +1000 |
---|---|---|
committer | Martin Pool <mbp@sourcefrog.net> | 2009-03-09 16:45:33 +1000 |
commit | c68c7ccf8643b684e36f2086001fca8cb3b49cf8 (patch) | |
tree | fcd4f2ab150af3b713d93f043c59b21092524648 | |
parent | d0bd56f20eb13e263ddba45a61b0d47690381209 (diff) | |
download | testscenarios-git-c68c7ccf8643b684e36f2086001fca8cb3b49cf8.tar.gz |
make check now runs doctests and they pass; documentation is therefore more correct
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | README | 147 | ||||
-rw-r--r-- | example/__init__.py | 1 | ||||
-rw-r--r-- | example/test_sample.py | 6 | ||||
-rw-r--r-- | run_doctest.py | 5 |
5 files changed, 112 insertions, 53 deletions
@@ -1,9 +1,13 @@ PYTHONPATH:=$(shell pwd)/lib:${PYTHONPATH} +PYTHON ?= python all: +# it would be nice to use doctest directly to run the README, but that's +# only supported from python2.6 onwards, so we need a script check: - PYTHONPATH=$(PYTHONPATH) python ./test_all.py $(TESTRULE) + PYTHONPATH=$(PYTHONPATH):.:./lib $(PYTHON) run_doctest.py README + PYTHONPATH=$(PYTHONPATH) $(PYTHON) ./test_all.py $(TESTRULE) clean: find . -name '*.pyc' -print0 | xargs -0 rm -f @@ -25,11 +25,13 @@ a single test suite) or for classic dependency injection (provide tests with dependencies externally to the test code itself, allowing easy testing in different situations). -Dependencies: -============= +Dependencies +============ * Python 2.4+ -* testtools +* testtools <https://launchpad.net/testtools> + + >>> import testtools Why TestScenarios @@ -52,8 +54,20 @@ It is the intent of testscenarios to make dynamically running a single test in multiple scenarios clear, easy to debug and work with even when the list of scenarios is dynamically generated. -Getting Scenarios applied: -========================== + +Defining Scenarios +================== + +A **scenario** is a tuple of a string name for the scenario, and a dict of +parameters describing the scenario. The name is appended to the test name, and +the parameters are made available to the test instance when it's run. + +Scenarios are presented in **scenario lists** which are typically Python lists +but may be any iterable. + + +Getting Scenarios applied +========================= At its heart the concept is simple. For a given test object with a list of scenarios we prepare a new test object for each scenario. This involves: @@ -88,10 +102,20 @@ useful test base classes, or need to override run() or __call__ yourself) then you can cause scenario application to happen later by calling ``testscenarios.generate_scenarios()``. For instance:: - >>> mytests = loader.loadTestsFromNames([...]) - >>> test_suite = TestSuite() + >>> import unittest + >>> from testscenarios.scenarios import generate_scenarios + +This can work with loaders and runners from the standard library, or possibly other +implementations:: + + >>> loader = unittest.TestLoader() + >>> test_suite = unittest.TestSuite() + >>> runner = unittest.TextTestRunner() + + >>> mytests = loader.loadTestsFromNames(['example.test_sample']) >>> test_suite.addTests(generate_scenarios(mytests)) >>> runner.run(test_suite) + <unittest._TextTestResult run=1 errors=0 failures=0> Testloaders +++++++++++ @@ -104,35 +128,37 @@ course, if you are using the subclassing approach this is already a surety). With ``load_tests``:: >>> def load_tests(standard_tests, module, loader): - >>> result = loader.suiteClass() - >>> result.addTests(generate_scenarios(standard_tests)) - >>> return result + ... result = loader.suiteClass() + ... result.addTests(generate_scenarios(standard_tests)) + ... return result With ``test_suite``:: >>> def test_suite(): - >>> loader = TestLoader() - >>> tests = loader.loadTestsFromName(__name__) - >>> result = loader.suiteClass() - >>> result.addTests(generate_scenarios(tests)) - >>> return result + ... loader = TestLoader() + ... tests = loader.loadTestsFromName(__name__) + ... result = loader.suiteClass() + ... result.addTests(generate_scenarios(tests)) + ... return result -Setting Scenarios for a test: -============================= +Setting Scenarios for a test +============================ A sample test using scenarios can be found in the doc/ folder. -See ``pydoc testscenarios`` for details. +See `pydoc testscenarios` for details. On the TestCase +++++++++++++++ You can set a scenarios attribute on the test case:: - >>> class MyTest(TestCase): - >>> - >>> scenarios = [scenario1, scenario2, ...] + >>> class MyTest(unittest.TestCase): + ... + ... scenarios = [ + ... ('scenario1', dict(param=1)), + ... ('scenario2', dict(param=2)),] This provides the main interface by which scenarios are found for a given test. Subclasses will inherit the scenarios (unless they override the attribute). @@ -145,22 +171,39 @@ not yet run. Simply replace (or alter, but be aware that many tests may share a single scenarios attribute) the scenarios attribute. For instance in this example some third party tests are extended to run with a custom scenario. :: - >>> for test in iterate_tests(stock_library_tests): - >>> if isinstance(test, TestVFS): - >>> test.scenarios = test.scenarios + [my_vfs_scenario] - >>> ... + >>> class TestTransport: + ... """Hypothetical test case for bzrlib transport tests""" + ... pass + ... + >>> stock_library_tests = unittest.TestLoader().loadTestsFromNames( + ... ['example.test_sample']) + ... + >>> for test in testtools.iterate_tests(stock_library_tests): + ... if isinstance(test, TestTransport): + ... test.scenarios = test.scenarios + [my_vfs_scenario] + ... + >>> suite = unittest.TestSuite() >>> suite.addTests(generate_scenarios(stock_library_tests)) -Note that adding scenarios to a test that has already been parameterised via -generate_scenarios generates a cross product:: - - >>> class CrossProductDemo(TestCase): - >>> scenarios = [scenario_0_0, scenario_0_1] - >>> def test_foo(self): - >>> return +Generated tests don't have a ``scenarios`` list, because they don't normally +require any more expansion. However, you can add a ``scenarios`` list back on +to them, and then run them through ``generate_scenarios`` again to generate the +cross product of tests. :: + + >>> class CrossProductDemo(unittest.TestCase): + ... scenarios = [('scenario_0_0', {}), + ... ('scenario_0_1', {})] + ... def test_foo(self): + ... return + ... + >>> suite = unittest.TestSuite() >>> suite.addTests(generate_scenarios(CrossProductDemo("test_foo"))) - >>> for test in iterate_tests(suite): - >>> test.scenarios = test.scenarios + [scenario_1_0, scenario_1_1] + >>> for test in testtools.iterate_tests(suite): + ... test.scenarios = [ + ... ('scenario_1_0', {}), + ... ('scenario_1_1', {})] + ... + >>> suite2 = unittest.TestSuite() >>> suite2.addTests(generate_scenarios(suite)) >>> print suite2.countTestCases() 4 @@ -176,25 +219,25 @@ For instance:: >>> hash_scenarios = [] >>> try: - >>> import md5 - >>> except ImportError: - >>> pass - >>> else: - >>> hash_scenarios.append(("md5", "hash": md5.new)) + ... from hashlib import md5 + ... except ImportError: + ... pass + ... else: + ... hash_scenarios.append(("md5", dict(hash=md5))) >>> try: - >>> import sha1 - >>> except ImportError: - >>> pass - >>> else: - >>> hash_scenarios.append(("sha1", "hash": sha1.new)) - >>> - >>> class TestHashContract(TestCase): - >>> - >>> scenarios = hash_scenarios - >>> - >>> class TestHashPerformance(TestCase): - >>> - >>> scenarios = hash_scenarios + ... from hashlib import sha1 + ... except ImportError: + ... pass + ... else: + ... hash_scenarios.append(("sha1", dict(hash=sha1))) + ... + >>> class TestHashContract(unittest.TestCase): + ... + ... scenarios = hash_scenarios + ... + >>> class TestHashPerformance(unittest.TestCase): + ... + ... scenarios = hash_scenarios Forcing Scenarios diff --git a/example/__init__.py b/example/__init__.py new file mode 100644 index 0000000..16da333 --- /dev/null +++ b/example/__init__.py @@ -0,0 +1 @@ +# contractual obligation diff --git a/example/test_sample.py b/example/test_sample.py new file mode 100644 index 0000000..5254ccb --- /dev/null +++ b/example/test_sample.py @@ -0,0 +1,6 @@ +import unittest + +class TestSample(unittest.TestCase): + + def test_so_easy(self): + pass diff --git a/run_doctest.py b/run_doctest.py new file mode 100644 index 0000000..0855484 --- /dev/null +++ b/run_doctest.py @@ -0,0 +1,5 @@ +import doctest +import sys + +for n in sys.argv: + print doctest.testfile(n, raise_on_error=False, ) |