{ "info": { "author": "Roman Imankulov", "author_email": "roman.imankulov@gmail.com", "bugtrack_url": null, "classifiers": [], "description": "Resources. A fixture lifecycle management for your tests\n========================================================\n\n.. image:: https://secure.travis-ci.org/Doist/resources.png?branch=master\n :alt: Build Status\n :target: https://secure.travis-ci.org/Doist/resources\n\nWhy do we need this library\n--------------------------------------------------\n\nWe are not satisfied with classical xUnit way of setup and teardown. We prefer\nconcise approach of py.test over the verbosity of standard unittest.\n\nWe found ourselves copying and pasting the same boilerplate code from one test\nto another or creating extensive structure of test class hierarchy.\n\npy.test fixtures, injected in test functions as parameter names, is\ndifferent approach for fixture management. It's neither worse nor better,\nbut we found it to be not as flexible as we need.\n\nSome questions, that we wanted to solve often, looked like:\n\n- I have a py.test fixture which creates a new user with default set of\n properties. Is there a way I can create a user with different name by the same\n fixture?\n- Is there a way to create two users in one test case with the same fixture?\n- Is there an easy recipe to create a user first, and then, say, a todo item for\n this particular user in another, separate, fixture?\n\nSure enough, we can handle or work around all these issues somehow with xUnit\nsetups and teardowns or py.test fixtures, but we wanted something more flexible,\neasy and convenient to use. That's why we created ``resources`` library.\n\n\nHow do we use it\n----------------\n\nFirst, we define functions which we call \"resource makers\". These makers are\nresponsible for creating and destroying resources. It's like setup and teardown\nin one callable.\n\n.. code-block:: python\n\n from resources import resources\n\n @resources.register_func\n def user(email='joe@example.com', password='password', username='Joe'):\n user = User.objects.create(email=email, password=password, username=username)\n try:\n yield user\n finally:\n user.delete()\n\nThe flow is simple: we create, we yield, we destroy.\n\nWe get a number of resource makers, and we group them into modules, like\n`tests/resources_core.py`, `tests/resources_users.py`, etc.\n\nThen, in a test file, where we plan to use resources, we import the same global\nobject, load resource modules we need, and activate them in tests.\n\n.. code-block:: python\n\n from resources import resources\n resources.register_mod('tests.resources_core')\n resources.register_mod('tests.resources_users')\n\n def test_user_properties():\n with resources.user_ctx() as user:\n assert user.username == 'Joe'\n\nThis is where a little bit of magic happens. Once you define and register the\nresource maker with name ``foo``, a context manager ``foo_ctx`` is created for\nyour convenience. This context manager creates a new resource instance with the\ncorresponding maker function, and destroys the object the way you defined, once\nthe code flow abandons a wrapping \"with\"-context.\n\nWhen it shines\n---------------\n\nAt this point and maybe not so exciting. Yeah, everyone can write the code like\nthis, the difference is that we actually *did it* :-). We also have a bunch\nof nifty features making the whole stuff more interesting.\n\nFeature 1. Customizeable resources\n----------------------------------\n\nContexts are better than py.test fixtures, because they are customizeable.\nProvide everything you need to context manager, and it will be passed to\nresource maker function as an arguments.\n\n.. code-block:: python\n\n def test_user_properties():\n with resources.user_ctx(name='Mary') as user:\n assert user.username == 'Mary'\n\n\nFeature 2. Global object scope and dependent resources\n------------------------------------------------------\n\nWe need to have access to resources at different stages of our tests: to get\naccess to object's properties and methods, to initiate another, dependent\nfixture instance, and finally to tear down everything.\n\nAs soon as you enter the context with ``resources.foo_ctx()`` a variable\n``resources.foo`` will be created and will be available from everywhere,\nincluding your test function, and other resource makers.\n\nThe latter fact is especially important, because it's the way we manage\ndependent resources. Yet we need some conventions, which resource is created\nfirst, and so on.\n\n.. code-block:: python\n\n @resources.register_func\n def todo_item(content='Foo'):\n item = TodoItem.objects.create(user=resources.user, content=content)\n\nWe agreed that we create user resource first, and todo item afterwards, and\ncreated a new resource maker, taking advantage of this convention.\n\nWe use it like this:\n\n.. code-block:: python\n\n def test_todo_item_properties():\n with resources.user_ctx(), resources.todo_item_ctx():\n assert resources.todo_item.content == 'Foo'\n\nBy the way, if you are still stuck with python2.6, several context managers in\nthe same \"with\" expression aren't available for you yet. Use ``contextlib.nested``\nto avoid deep indentation.\n\n\nFeature 3. Several resources of the same class, and tuneable resource names\n---------------------------------------------------------------------------\n\nSometimes we need to create a couple of resources of the same type, instead of\njust one instance. It's not a problem, if you don't want to use global\nnamespace to get access to them. Otherwise you must create a unique identifier\nfor every resource.\n\nActually, it's trivial. All you should do is provide a special `_name` attribute\nto context manager constructor. This attribute won't be passed to your resource\nmaker function.\n\n.. code-block:: python\n\n def test_a_couple_of_users():\n with resources.user_ctx(username='Adam', _name='adam'), \\\n resurces.user_ctx(username='Eve', _name='eve'):\n assert resources.adam.username == 'Adam'\n assert resources.eve.username == 'Eve'\n\n\nFeature 4. Function decorators\n------------------------------\n\nContext manager can work as a decorator too. When we use it like this, an extra\nargument will be passed to the function.\n\n.. code-block:: python\n\n @resources.user_ctx()\n def test_user_properties(user):\n assert user.username == 'Joe'\n\nWe should say that usually it works, but to make it work along with py.test\nwhich performs deep introspection of function signatures, we made in with some\n\"dirty hacks\" inside, and you may find out that in some cases the chain of\ndecorators dies with a misleading exception. We'd recommend to use context\nmanagers instead of decorators, wherever possible.\n\nFeature 5. Resource managers\n----------------------------\n\nYes, we do use setup and teardown methods too. If every function in your test\nsuite uses the same set of resources, it would be counterproductive to write\nthe same chain of decorators or context managers over and over again.\n\nIn this case we use another concept: resource managers. Every resource maker\n``foo`` creates the ``resources.foo_mgr`` instance, having `start` and\n`stop` methods. The `start` method accepts all arguments which\nthe `foo_ctx` function does, including special `_name` argument.\nThe `stop` method has only one optional `_name` argument, and is used to\ndestroy previously created instance.\n\nHere is a py.test example\n\n.. code-block:: python\n\n def setup_function(func):\n resources.user_mgr.start(username='Mary')\n\n def test_user_properties():\n assert resources.user.username == 'Mary'\n\n def teardown_function(func):\n resources.user_mgr.stop()\n\n\nFeature 6. Built-in console and debugger\n----------------------------------------\n\nSometimes it's nice to take a look on what's going on within test function and\nget access at some point to python console or debugger.\n\nUsually you probably do something like\n\n.. code-block:: python\n\n import pdb; pdb.set_trace()\n\nOr, if you need to get shell and have IPython installed\n\n.. code-block:: python\n\n from IPython import embed; embed()\n\nAs it happens often, we added to resources two functions, launching either\ndebugger or python console inside your test function.\n\n.. code-block:: python\n\n from resources import resources\n\n def test_something():\n resources.pdb() # to launch debugger\n resources.shell() # to launch Python REPL\n\nIf you install IPython and ipdb (`pip install IPython ipdb`), you get more\nfriendly versions of consoles, otherwise resources fall back to built-in python\nconsole and debugger.\n\nLaunch `py.test` with `-s` switch to be able to fall into interactive console.\n\nIt's especially cool that resources object is autocomplete-friendly and it\nworks well in IPython\n\n.. code-block:: python\n\n\n In [1]: resources.\n resources.john resources.pdb resources.register_mod\n resources.mary resources.register_func resources.shell\n\n In [1]: resources.mary\n Out[1]: {'name': 'Mary Moe'}\n\n In [2]: resources.user_mgr.start()\n Out[2]: {'name': 'John Doe'}\n\n In [3]: resources.todo\n resources.todo_item_ctx resources.todo_item_mgr\n\n In [3]: resources.todo_item_mgr.start()\n Out[3]: {'text': 'Do something', 'user': {'name': 'John Doe'}}\n\n In [4]: resources.todo\n resources.todo_item resources.todo_item_ctx resources.todo_item_mgr\n\n In [4]: resources.todo_item\n Out[4]: {'text': 'Do something', 'user': {'name': 'John Doe'}}\n\n\n\nFeature 7. Globally accessible storage of constants\n---------------------------------------------------\n\nThis feature is not something unique to `resources` module. Pretty much every\nobject can act this way, but it is handy to have a convention about the\nway you store your test-related constants.\n\nIt may work like this.\n\n.. code-block::\n\n resources.TEST_DIRECTORY = '/tmp/foo'\n resources.DOMAIN_NAME = 'example.com'\n resources.SECRET_KEY = 'foobar'\n\nAnd then, in the test file.\n\n.. code-block::\n\n from resources import resources\n resoures.register_mod('')\n\n def test_constants():\n assert resources.TEST_DIRECTORY == '/tmp/foo'\n assert resources.DOMAIN_NAME == 'example.com'\n assert resources.SECRET_KEY == 'foobar'\n\n\nConclusion\n----------\n\nThe `resources` library works for us in py.test environment. We don't see any\nreasons why it shouldn't work the same way with nose or classic unitttests.\nIt works for python versions 2.6, 2.7 and 3.3.\n\nPlease bear in mind that the library *is not thread safe*, as we are happy with\nsingle threaded tests at this time.\n\nAnd after all... Seven extra features to improve your test suites for free! What\nare you waiting for? It's already improved the quailty of our lives in\n`Doist Inc `_, and we do hope it will do the same for your\nprojects.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/doist/resources", "keywords": null, "license": "BSD", "maintainer": null, "maintainer_email": null, "name": "python-resources", "package_url": "https://pypi.org/project/python-resources/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/python-resources/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/doist/resources" }, "release_url": "https://pypi.org/project/python-resources/0.3/", "requires_dist": null, "requires_python": null, "summary": "A fixture lifecycle management library for your tests", "version": "0.3" }, "last_serial": 740583, "releases": { "0.2": [ { "comment_text": "", "digests": { "md5": "daedcf32bb8e69e42273ba588fd46a37", "sha256": "0cd4918056892f8dd4d44f46cd4c208e44b47e3fee8a4b37f296cbf79cc7d684" }, "downloads": -1, "filename": "python-resources-0.2.tar.gz", "has_sig": false, "md5_digest": "daedcf32bb8e69e42273ba588fd46a37", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7371, "upload_time": "2013-02-25T07:44:32", "url": "https://files.pythonhosted.org/packages/29/05/2fed01d2c0c62f2d8a55b8a92aae6eea4c02e0d5bec929286875d708b33b/python-resources-0.2.tar.gz" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "c91c18f0a3a42030de9850ef9377c936", "sha256": "727d0aadcf88e1af0eb5eb9ff03d92e244a8d08d9847c6b3efb87506276609a6" }, "downloads": -1, "filename": "python-resources-0.3.tar.gz", "has_sig": false, "md5_digest": "c91c18f0a3a42030de9850ef9377c936", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8286, "upload_time": "2013-03-22T18:52:52", "url": "https://files.pythonhosted.org/packages/32/fb/9c7f74f74a02fbe820ac240df6be4a8e5f012931dfbb437330a34327f74d/python-resources-0.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "c91c18f0a3a42030de9850ef9377c936", "sha256": "727d0aadcf88e1af0eb5eb9ff03d92e244a8d08d9847c6b3efb87506276609a6" }, "downloads": -1, "filename": "python-resources-0.3.tar.gz", "has_sig": false, "md5_digest": "c91c18f0a3a42030de9850ef9377c936", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8286, "upload_time": "2013-03-22T18:52:52", "url": "https://files.pythonhosted.org/packages/32/fb/9c7f74f74a02fbe820ac240df6be4a8e5f012931dfbb437330a34327f74d/python-resources-0.3.tar.gz" } ] }