{ "info": { "author": "Testing-cabal", "author_email": "testing-cabal@lists.launchpad.net", "bugtrack_url": null, "classifiers": [ "Development Status :: 6 - Mature", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Testing" ], "description": "*****************************************************************\ntestscenarios: extensions to python unittest to support scenarios\n*****************************************************************\n\n Copyright (c) 2009, Robert Collins \n \n Licensed under either the Apache License, Version 2.0 or the BSD 3-clause\n license at the users choice. A copy of both licenses are available in the\n project source as Apache-2.0 and BSD. You may not use this file except in\n compliance with one of these two licences.\n \n Unless required by applicable law or agreed to in writing, software\n distributed under these licenses is distributed on an \"AS IS\" BASIS, WITHOUT\n WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n license you chose for the specific language governing permissions and\n limitations under that license.\n\n\ntestscenarios provides clean dependency injection for python unittest style\ntests. This can be used for interface testing (testing many implementations via\na single test suite) or for classic dependency injection (provide tests with\ndependencies externally to the test code itself, allowing easy testing in\ndifferent situations).\n\nDependencies\n============\n\n* Python 2.6+\n* testtools \n\n\nWhy TestScenarios\n=================\n\nStandard Python unittest.py provides on obvious method for running a single\ntest_foo method with two (or more) scenarios: by creating a mix-in that\nprovides the functions, objects or settings that make up the scenario. This is\nhowever limited and unsatisfying. Firstly, when two projects are cooperating\non a test suite (for instance, a plugin to a larger project may want to run\nthe standard tests for a given interface on its implementation), then it is\neasy for them to get out of sync with each other: when the list of TestCase\nclasses to mix-in with changes, the plugin will either fail to run some tests\nor error trying to run deleted tests. Secondly, its not as easy to work with\nruntime-created-subclasses (a way of dealing with the aforementioned skew)\nbecause they require more indirection to locate the source of the test, and will\noften be ignored by e.g. pyflakes pylint etc.\n\nIt is the intent of testscenarios to make dynamically running a single test\nin multiple scenarios clear, easy to debug and work with even when the list\nof scenarios is dynamically generated.\n\n\nDefining Scenarios\n==================\n\nA **scenario** is a tuple of a string name for the scenario, and a dict of\nparameters describing the scenario. The name is appended to the test name, and\nthe parameters are made available to the test instance when it's run.\n\nScenarios are presented in **scenario lists** which are typically Python lists\nbut may be any iterable.\n\n\nGetting Scenarios applied\n=========================\n\nAt its heart the concept is simple. For a given test object with a list of\nscenarios we prepare a new test object for each scenario. This involves:\n\n* Clone the test to a new test with a new id uniquely distinguishing it.\n* Apply the scenario to the test by setting each key, value in the scenario\n as attributes on the test object.\n\nThere are some complicating factors around making this happen seamlessly. These\nfactors are in two areas:\n\n* Choosing what scenarios to use. (See Setting Scenarios For A Test).\n* Getting the multiplication to happen. \n\nSubclasssing\n++++++++++++\n\nIf you can subclass TestWithScenarios, then the ``run()`` method in\nTestWithScenarios will take care of test multiplication. It will at test\nexecution act as a generator causing multiple tests to execute. For this to \nwork reliably TestWithScenarios must be first in the MRO and you cannot\noverride run() or __call__. This is the most robust method, in the sense\nthat any test runner or test loader that obeys the python unittest protocol\nwill run all your scenarios.\n\nManual generation\n+++++++++++++++++\n\nIf you cannot subclass TestWithScenarios (e.g. because you are using\nTwistedTestCase, or TestCaseWithResources, or any one of a number of other\nuseful test base classes, or need to override run() or __call__ yourself) then \nyou can cause scenario application to happen later by calling\n``testscenarios.generate_scenarios()``. For instance::\n\n >>> import unittest\n >>> try:\n ... from StringIO import StringIO\n ... except ImportError:\n ... from io import StringIO\n >>> from testscenarios.scenarios import generate_scenarios\n\nThis can work with loaders and runners from the standard library, or possibly other\nimplementations::\n\n >>> loader = unittest.TestLoader()\n >>> test_suite = unittest.TestSuite()\n >>> runner = unittest.TextTestRunner(stream=StringIO())\n\n >>> mytests = loader.loadTestsFromNames(['doc.test_sample'])\n >>> test_suite.addTests(generate_scenarios(mytests))\n >>> runner.run(test_suite)\n \n\nTestloaders\n+++++++++++\n\nSome test loaders support hooks like ``load_tests`` and ``test_suite``.\nEnsuring your tests have had scenario application done through these hooks can\nbe a good idea - it means that external test runners (which support these hooks\nlike ``nose``, ``trial``, ``tribunal``) will still run your scenarios. (Of\ncourse, if you are using the subclassing approach this is already a surety).\nWith ``load_tests``::\n\n >>> def load_tests(standard_tests, module, loader):\n ... result = loader.suiteClass()\n ... result.addTests(generate_scenarios(standard_tests))\n ... return result\n\nas a convenience, this is available in ``load_tests_apply_scenarios``, so a\nmodule using scenario tests need only say ::\n\n >>> from testscenarios import load_tests_apply_scenarios as load_tests\n\nPython 2.7 and greater support a different calling convention for `load_tests``\n. `load_tests_apply_scenarios`\ncopes with both.\n\nWith ``test_suite``::\n\n >>> def test_suite():\n ... loader = TestLoader()\n ... tests = loader.loadTestsFromName(__name__)\n ... result = loader.suiteClass()\n ... result.addTests(generate_scenarios(tests))\n ... return result\n\n\nSetting Scenarios for a test\n============================\n\nA sample test using scenarios can be found in the doc/ folder.\n\nSee `pydoc testscenarios` for details.\n\nOn the TestCase\n+++++++++++++++\n\nYou can set a scenarios attribute on the test case::\n\n >>> class MyTest(unittest.TestCase):\n ...\n ... scenarios = [\n ... ('scenario1', dict(param=1)),\n ... ('scenario2', dict(param=2)),]\n\nThis provides the main interface by which scenarios are found for a given test.\nSubclasses will inherit the scenarios (unless they override the attribute).\n\nAfter loading\n+++++++++++++\n\nTest scenarios can also be generated arbitrarily later, as long as the test has\nnot yet run. Simply replace (or alter, but be aware that many tests may share a\nsingle scenarios attribute) the scenarios attribute. For instance in this\nexample some third party tests are extended to run with a custom scenario. ::\n\n >>> import testtools\n >>> class TestTransport:\n ... \"\"\"Hypothetical test case for bzrlib transport tests\"\"\"\n ... pass\n ...\n >>> stock_library_tests = unittest.TestLoader().loadTestsFromNames(\n ... ['doc.test_sample'])\n ...\n >>> for test in testtools.iterate_tests(stock_library_tests):\n ... if isinstance(test, TestTransport):\n ... test.scenarios = test.scenarios + [my_vfs_scenario]\n ...\n >>> suite = unittest.TestSuite()\n >>> suite.addTests(generate_scenarios(stock_library_tests))\n\nGenerated tests don't have a ``scenarios`` list, because they don't normally\nrequire any more expansion. However, you can add a ``scenarios`` list back on\nto them, and then run them through ``generate_scenarios`` again to generate the\ncross product of tests. ::\n\n >>> class CrossProductDemo(unittest.TestCase):\n ... scenarios = [('scenario_0_0', {}),\n ... ('scenario_0_1', {})]\n ... def test_foo(self):\n ... return\n ...\n >>> suite = unittest.TestSuite()\n >>> suite.addTests(generate_scenarios(CrossProductDemo(\"test_foo\")))\n >>> for test in testtools.iterate_tests(suite):\n ... test.scenarios = [\n ... ('scenario_1_0', {}), \n ... ('scenario_1_1', {})]\n ...\n >>> suite2 = unittest.TestSuite()\n >>> suite2.addTests(generate_scenarios(suite))\n >>> print(suite2.countTestCases())\n 4\n\nDynamic Scenarios\n+++++++++++++++++\n\nA common use case is to have the list of scenarios be dynamic based on plugins\nand available libraries. An easy way to do this is to provide a global scope\nscenarios somewhere relevant to the tests that will use it, and then that can\nbe customised, or dynamically populate your scenarios from a registry etc.\nFor instance::\n\n >>> hash_scenarios = []\n >>> try:\n ... from hashlib import md5\n ... except ImportError:\n ... pass\n ... else:\n ... hash_scenarios.append((\"md5\", dict(hash=md5)))\n >>> try:\n ... from hashlib import sha1\n ... except ImportError:\n ... pass\n ... else:\n ... hash_scenarios.append((\"sha1\", dict(hash=sha1)))\n ...\n >>> class TestHashContract(unittest.TestCase):\n ...\n ... scenarios = hash_scenarios\n ...\n >>> class TestHashPerformance(unittest.TestCase):\n ...\n ... scenarios = hash_scenarios\n\n\nForcing Scenarios\n+++++++++++++++++\n\nThe ``apply_scenarios`` function can be useful to apply scenarios to a test\nthat has none applied. ``apply_scenarios`` is the workhorse for\n``generate_scenarios``, except it takes the scenarios passed in rather than\nintrospecting the test object to determine the scenarios. The\n``apply_scenarios`` function does not reset the test scenarios attribute,\nallowing it to be used to layer scenarios without affecting existing scenario\nselection.\n\n\nGenerating Scenarios\n====================\n\nSome functions (currently one :-) are available to ease generation of scenario\nlists for common situations.\n\nTesting Per Implementation Module\n+++++++++++++++++++++++++++++++++\n\nIt is reasonably common to have multiple Python modules that provide the same\ncapabilities and interface, and to want apply the same tests to all of them.\n\nIn some cases, not all of the statically defined implementations will be able\nto be used in a particular testing environment. For example, there may be both\na C and a pure-Python implementation of a module. You want to test the C\nmodule if it can be loaded, but also to have the tests pass if the C module has\nnot been compiled.\n\nThe ``per_module_scenarios`` function generates a scenario for each named\nmodule. The module object of the imported module is set in the supplied\nattribute name of the resulting scenario.\nModules which raise ``ImportError`` during import will have the\n``sys.exc_info()`` of the exception set instead of the module object. Tests\ncan check for the attribute being a tuple to decide what to do (e.g. to skip).\n\nNote that for the test to be valid, all access to the module under test must go\nthrough the relevant attribute of the test object. If one of the\nimplementations is also directly imported by the test module or any other,\ntestscenarios will not magically stop it being used.\n\n\nAdvice on Writing Scenarios\n===========================\n\nIf a parameterised test is because of a bug run without being parameterized,\nit should fail rather than running with defaults, because this can hide bugs.\n\n\nProducing Scenarios\n===================\n\nThe `multiply_scenarios` function produces the cross-product of the scenarios\npassed in::\n\n >>> from testscenarios.scenarios import multiply_scenarios\n >>> \n >>> scenarios = multiply_scenarios(\n ... [('scenario1', dict(param1=1)), ('scenario2', dict(param1=2))],\n ... [('scenario2', dict(param2=1))],\n ... )\n >>> scenarios == [('scenario1,scenario2', {'param2': 1, 'param1': 1}),\n ... ('scenario2,scenario2', {'param2': 1, 'param1': 2})]\n True", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://launchpad.net/testscenarios", "keywords": null, "license": "UNKNOWN", "maintainer": null, "maintainer_email": null, "name": "testscenarios", "package_url": "https://pypi.org/project/testscenarios/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/testscenarios/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://launchpad.net/testscenarios" }, "release_url": "https://pypi.org/project/testscenarios/0.5.0/", "requires_dist": null, "requires_python": null, "summary": "Testscenarios, a pyunit extension for dependency injection", "version": "0.5.0" }, "last_serial": 1531981, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "10126c2d79a9decfb2dac7818c8e8e2e", "sha256": "bf4c9b34d1e754f50d3cd9d56ae85704dbc5543bfd588f42d9b91aa3922ab6b6" }, "downloads": -1, "filename": "testscenarios-0.1.tar.gz", "has_sig": true, "md5_digest": "10126c2d79a9decfb2dac7818c8e8e2e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13605, "upload_time": "2009-12-22T04:09:28", "url": "https://files.pythonhosted.org/packages/3e/c5/65724db71de381797b553c53f47310d71707cfe0be17bb543c7eb368cd6f/testscenarios-0.1.tar.gz" } ], "0.2": [ { "comment_text": "", "digests": { "md5": "f6abf429eba815991dd5805502d4e7b9", "sha256": "118ef6b05c1fff431ae5e0f57e8e01a51be7d3d5acf9bbd3b11549e0693b6c48" }, "downloads": -1, "filename": "testscenarios-0.2.tar.gz", "has_sig": true, "md5_digest": "f6abf429eba815991dd5805502d4e7b9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 17442, "upload_time": "2010-02-01T06:06:00", "url": "https://files.pythonhosted.org/packages/d5/f3/c8b98fe161b2b2470cd44ecdd53943c03c1ebee244a97ec59fb97988235e/testscenarios-0.2.tar.gz" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "d4cce969db11c615f7ac77e7e9ea72c5", "sha256": "c4774cc839323bb037f5ee71a7212682ee72402f8f88a6b1e1a9e3499e18700d" }, "downloads": -1, "filename": "testscenarios-0.3.tar.gz", "has_sig": true, "md5_digest": "d4cce969db11c615f7ac77e7e9ea72c5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18507, "upload_time": "2012-04-04T12:48:07", "url": "https://files.pythonhosted.org/packages/6b/b0/3144985f9489c3157a3848ea600f7e884f59d79f495c904b2560309a90ed/testscenarios-0.3.tar.gz" } ], "0.4": [ { "comment_text": "", "digests": { "md5": "433cb8cd4d444b0deded3787240ee586", "sha256": "4feeee84f7fd8a6258fc00671e1521f80cb68d2fec1e2908b3ab52bcf396e198" }, "downloads": -1, "filename": "testscenarios-0.4.tar.gz", "has_sig": true, "md5_digest": "433cb8cd4d444b0deded3787240ee586", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22768, "upload_time": "2013-01-27T00:33:08", "url": "https://files.pythonhosted.org/packages/7c/e3/13a4c91a321778a0eb5e1710d4392aa12c80dbdb21a189b6ccb8d6c91fe7/testscenarios-0.4.tar.gz" } ], "0.5.0": [ { "comment_text": "", "digests": { "md5": "37a17934a8e3e6498c0d76b72932310a", "sha256": "480263fa5d6e618125bdf092aab129a3aeed5996b1e668428f12cc56d6d01d28" }, "downloads": -1, "filename": "testscenarios-0.5.0-py2.py3-none-any.whl", "has_sig": true, "md5_digest": "37a17934a8e3e6498c0d76b72932310a", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 21002, "upload_time": "2015-05-04T01:37:23", "url": "https://files.pythonhosted.org/packages/da/25/2f10da0d5427989fefa5ab51e697bc02625bbb7de2be3bc8452462efac78/testscenarios-0.5.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "859073d9e7b049aee2e6704c51f6001a", "sha256": "c257cb6b90ea7e6f8fef3158121d430543412c9a87df30b5dde6ec8b9b57a2b6" }, "downloads": -1, "filename": "testscenarios-0.5.0.tar.gz", "has_sig": true, "md5_digest": "859073d9e7b049aee2e6704c51f6001a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20951, "upload_time": "2015-05-04T01:37:16", "url": "https://files.pythonhosted.org/packages/f0/de/b0b5b98c0f38fd7086d082c47fcb455eedd39a044abe7c595f5f40cd6eed/testscenarios-0.5.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "37a17934a8e3e6498c0d76b72932310a", "sha256": "480263fa5d6e618125bdf092aab129a3aeed5996b1e668428f12cc56d6d01d28" }, "downloads": -1, "filename": "testscenarios-0.5.0-py2.py3-none-any.whl", "has_sig": true, "md5_digest": "37a17934a8e3e6498c0d76b72932310a", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 21002, "upload_time": "2015-05-04T01:37:23", "url": "https://files.pythonhosted.org/packages/da/25/2f10da0d5427989fefa5ab51e697bc02625bbb7de2be3bc8452462efac78/testscenarios-0.5.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "859073d9e7b049aee2e6704c51f6001a", "sha256": "c257cb6b90ea7e6f8fef3158121d430543412c9a87df30b5dde6ec8b9b57a2b6" }, "downloads": -1, "filename": "testscenarios-0.5.0.tar.gz", "has_sig": true, "md5_digest": "859073d9e7b049aee2e6704c51f6001a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20951, "upload_time": "2015-05-04T01:37:16", "url": "https://files.pythonhosted.org/packages/f0/de/b0b5b98c0f38fd7086d082c47fcb455eedd39a044abe7c595f5f40cd6eed/testscenarios-0.5.0.tar.gz" } ] }