'py.test: how to get the current test's name from the setup method?

I am using py.test and wonder if/how it is possible to retrieve the name of the currently executed test within the setup method that is invoked before running each test. Consider this code:

class TestSomething(object):

    def setup(self):
        test_name = ...

    def teardown(self):
        pass

    def test_the_power(self):
        assert "foo" != "bar"

    def test_something_else(self):
        assert True

Right before TestSomething.test_the_power becomes executed, I would like to have access to this name in setup as outlined in the code via test_name = ... so that test_name == "TestSomething.test_the_power".

Actually, in setup, I allocate some resource for each test. In the end, looking at the resources that have been created by various unit tests, I would like to be able to see which one was created by which test. Best thing would be to just use the test name upon creation of the resource.



Solution 1:[1]

You can also do this using the Request Fixture like this:

def test_name1(request):
    testname = request.node.name
    assert testname == 'test_name1'

Solution 2:[2]

You can also use the PYTEST_CURRENT_TEST environment variable set by pytest for each test case.

PYTEST_CURRENT_TEST environment variable

To get just the test name:

os.environ.get('PYTEST_CURRENT_TEST').split(':')[-1].split(' ')[0]

Solution 3:[3]

The setup and teardown methods seem to be legacy methods for supporting tests written for other frameworks, e.g. nose. The native pytest methods are called setup_method as well as teardown_method which receive the currently executed test method as an argument. Hence, what I want to achieve, can be written like so:

class TestSomething(object):

    def setup_method(self, method):
        print "\n%s:%s" % (type(self).__name__, method.__name__)

    def teardown_method(self, method):
        pass

    def test_the_power(self):
        assert "foo" != "bar"

    def test_something_else(self):
        assert True

The output of py.test -s then is:

============================= test session starts ==============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
plugins: cov
collected 2 items 

test_pytest.py 
TestSomething:test_the_power
.
TestSomething:test_something_else
.

=========================== 2 passed in 0.03 seconds ===========================

Solution 4:[4]

Short answer:

  • Use fixture called request
  • This fixture has the following interesting attributes:
    • request.node.originalname = the name of the function/method
    • request.node.name = name of the function/method and ids of the parameters
    • request.node.nodeid = relative path to the test file, name of the test class (if in a class), name of the function/method and ids of the parameters

Long answer:

I inspected the content of request.node. Here are the most interesting attributes I found:

class TestClass:

    @pytest.mark.parametrize("arg", ["a"])
    def test_stuff(self, request, arg):
        print("originalname:", request.node.originalname)
        print("name:", request.node.name)
        print("nodeid:", request.node.nodeid)

Prints the following:

 originalname: test_stuff
 name: test_stuff[a]
 nodeid: relative/path/to/test_things.py::TestClass::test_stuff[a]

NodeID is the most promising if you want to completely identify the test (including the parameters). Note that if the test is as a function (instead of in a class), the class name (::TestClass) is simply missing.

You can parse nodeid as you wish, for example:

components = request.node.nodeid.split("::")
filename = components[0]
test_class = components[1] if len(components) == 3 else None
test_func_with_params = components[-1]
test_func = test_func_with_params.split('[')[0]
test_params = test_func_with_params.split('[')[1][:-1].split('-')

In my example this results to:

filename = 'relative/path/to/test_things.py'
test_class = 'TestClass'
test_func = 'test_stuff'
test_params = ['a']

Solution 5:[5]

You could give the inspect module are try.

import inspect

def foo():
    print "My name is: ", inspect.stack()[0][3]


foo()

Output: My name is: foo

Solution 6:[6]

Try my little wrapper function which returns the full name of the test, the file and the test name. You can use whichever you like later. I used it within conftest.py where fixtures do not work as far as I know.

def get_current_test():
    full_name = os.environ.get('PYTEST_CURRENT_TEST').split(' ')[0]
    test_file = full_name.split("::")[0].split('/')[-1].split('.py')[0]
    test_name = full_name.split("::")[1]

    return full_name, test_file, test_name

Solution 7:[7]

# content of conftest.py

@pytest.fixture(scope='function', autouse=True)
def test_log(request):
    # Here logging is used, you can use whatever you want to use for logs
    log.info("STARTED Test '{}'".format(request.node.name))  

    def fin():
        log.info("COMPLETED Test '{}' \n".format(request.node.name))

    request.addfinalizer(fin)

Solution 8:[8]

You might have multiple tests, in which case...

test_names = [n for n in dir(self) if n.startswith('test_')]

...will give you all the functions and instance variables that begin with "test_" in self. As long as you don't have any variables named "test_something" this will work.

You can also define a method setup_method(self, method) instead of setup(self) and that will be called before each test method invocation. Using this, you're simply given each method as a parameter. See: http://pytest.org/latest/xunit_setup.html

Solution 9:[9]

Try type(self).__name__ perhaps?

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Garrett
Solution 2 Joao Coelho
Solution 3 Dr. Jan-Philip Gehrcke
Solution 4
Solution 5 Delimitry
Solution 6 matt3o
Solution 7
Solution 8
Solution 9 kindall