Erotemic Erotemic - 3 years ago 111
Python Question

How to construct an instance of _pytest.pytester.Testdir

I'm attempting to do some debugging (specifically on pytest/testing/test_doctest.py) and I want to step through some code in IPython. I have experience with pytest, but I never do anything too fancy with it, so I've never delved to deep into the more "magic" things it does.

In the test that I want to step through (potentially introspecting some of the objects), there is an argument called

testdir
, but nowhere in this file does it reference what
testdir
is or how I could possibly construct one.

After doing some digging it seems this is some magic fixture that automatically gets constructed and send to your function as a parameter, when you execute pytest with the
pytester
plugin. When I tracked down that class, it is constructed again via some magic
request
param, where the code is massively unhelpful in telling you what that magic
request
is or how to make one.

To make this concrete I simply want to take a test like this one:

def test_reportinfo(self, testdir):
'''
Test case to make sure that DoctestItem.reportinfo() returns lineno.
'''
p = testdir.makepyfile(test_reportinfo="""
def foo(x):
'''
>>> foo('a')
'b'
'''
return 'c'
""")
items, reprec = testdir.inline_genitems(p, '--doctest-modules')
reportinfo = items[0].reportinfo()
assert reportinfo[1] == 1


and run its logic in IPython. Looking at what the
testdir
object does, it seems pretty cool. It automatically makes a file for you and runs pytest problematically instead of via the command line. How can I make one of these? Is there some documentation I missed that makes how to do this clear and seem less obfuscated?

If I wanted to use something like this is my tests is there a way I could make what the magic
testdir
parameter is slightly more explicit so the next coder that looks at it isn't pulling his/her hair out like I am?

Answer Source

After much agonizing, I've figured out how to instantiate a fixture value.

    import _pytest
    config = _pytest.config._prepareconfig(['-s'])
    session = _pytest.main.Session(config)
    _pytest.tmpdir.pytest_configure(config)
    _pytest.fixtures.pytest_sessionstart(session)
    _pytest.runner.pytest_sessionstart(session)

    def func(testdir):
        return testdir

    parent = _pytest.python.Module('parent', config=config, session=session)
    function = _pytest.python.Function(
        'func', parent, callobj=func, config=config, session=session)
    _pytest.fixtures.fillfixtures(function)
    testdir = function.funcargs['testdir']

The main idea is to create a dummy pytest session. This is a bit tricky. Its critical that the ['-s'] is passed into _prepareconfig otherwise this will not print stdout, or crash when run in IPython.

Given a barebones config and session, the next step is to manually load in whatever fixture functionality you are going to use. This amounts to manually calling the hooks that pluggy usually takes care of for you. I found these by looking at the attribute error I got when trying to run code without them. Usually its just due to session or config lacking a required attribute. There may be a better way to go about doing this (aka automatically via pluggy).

Next, we create a function that requests the specific fixture we are interested in. Its up to you to know what these names are. Finally we setup a dummy module / function tree structure and call fillfixtures, which does the magic. The funcargs then contains a dictionary of these objects ready for use. Be careful if you expect some teardown functionality. I'm not sure if this covers that, but I don't really need it for what I'm doing.

Hope this helps someone else. Note: this talk helped me understand what was happening in pytest under the hood a bit better: https://www.youtube.com/watch?v=zZsNPDfOoHU

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download