60

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.

Dr. Jan-Philip Gehrcke
  • 33,287
  • 14
  • 85
  • 130

9 Answers9

122

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

def test_name1(request):
    testname = request.node.name
    assert testname == 'test_name1'
Garrett
  • 4,007
  • 2
  • 41
  • 59
danielfrg
  • 2,597
  • 2
  • 22
  • 23
  • 18
    **This is the general-purpose answer,** equally applicable to both test functions _and_ test methods. While py.test documentation is _usually_ spectacular, its documentation for the `request` fixture is unusually awful. The `request.node` attribute is documented only as "underlying collection node (depends on current request scope)." That's it. I absolutely _never_ would have found this – and doubt anyone else would have either. Bravo, froggy Daniel! – Cecil Curry May 23 '16 at 03:46
  • 26
    Note however when the test function has been parametrized, `request.node.name` contains the unique name with parameters, e.g. `test_name[param-other_param]`. Therefore it is _safer_ to always use [`request.node.originalname`](http://doc.pytest.org/en/latest/writing_plugins.html#_pytest.python.Function.originalname), which provides the original function name intact. Also note this is available starting from py.test 3.0. – julen Dec 12 '16 at 10:46
  • 4
    There is no mention of the request fixture at your link. – OrangeDog May 04 '18 at 14:28
  • @julen but if the test is *not* parameterized, `originalname` is `None` – OrangeDog May 04 '18 at 14:30
  • `Note however when the test function has been parametrized, request.node.name contains the unique name with parameters, e.g. test_name[param-other_param].` For me, that's what I want, because I want a unique output filename. – Polv Jul 17 '18 at 00:28
  • 5
    You can also get the module name with `request.module.__name__`. – Colin Schoen Jan 18 '19 at 20:56
  • 1
    Just noting that the documentation is now much clearer on the use of this: https://docs.pytest.org/en/documentation-restructure/how-to/fixture.html#fixtures-can-introspect-the-requesting-test-context – robo Mar 25 '21 at 13:55
63

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]
Joao Coelho
  • 2,838
  • 4
  • 30
  • 36
  • 7
    Wow, this should be the simplest answer and should be accepted – Nam G VU Dec 19 '18 at 12:33
  • Just tested with pytest 4.0.2 and it does work. You might also want to add `.strip('[]')` to remove more extraneous info. PS: You need to add the code _inside_ the test case. – Joao Coelho Dec 19 '18 at 22:02
  • I mean the env varia le not define. Do we need to config pytest somewhere for this? – Nam G VU Dec 20 '18 at 00:45
  • not sure what you mean.. that env variable is defined by Pytest when running tests. See example here (variable values in gray to the right of the code): https://drive.google.com/file/d/1yg8r8R3LBxG9RtEcktcWYKyPiWR6LrNJ/view?usp=sharing – Joao Coelho Dec 20 '18 at 23:38
  • 1
    Oh, now I see that it's from the `setup` method.. not sure if that env var is defined at that point. an alternative is to just run that piece of code (or call it as a separate function) in the beginning of the test. – Joao Coelho Dec 20 '18 at 23:43
  • Thank you verymuch great tips, I get the testname on to my conftest.py ... thanks again for the link and trick. – Buddhika Chaturanga Jan 21 '19 at 18:27
  • Love this answer! – laike9m Jul 22 '20 at 08:09
  • This is the best answer I've seen to this particular issue and I've been searching for at least some hours. Thank Joao – imp Dec 01 '20 at 13:19
  • 4
    Note The contents of PYTEST_CURRENT_TEST is meant to be human readable and the actual format can be changed between releases (even bug fixes) so it shouldn’t be relied on for scripting or automation. – zowers Mar 16 '21 at 15:56
  • Is it reliable in the context when multiple test scenario are running in parallel, for example with pytest-xdist? – Valentyn Aug 15 '23 at 00:30
  • Haven't tried w/ `xdist`. If you want to be certain, use [this solution](https://stackoverflow.com/a/34732269/205075), using `request.node.name`. – Joao Coelho Aug 15 '23 at 12:48
22

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 ===========================
Dr. Jan-Philip Gehrcke
  • 33,287
  • 14
  • 85
  • 130
12

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']
miksus
  • 2,426
  • 1
  • 18
  • 34
4
# 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)
Shivam Bharadwaj
  • 1,864
  • 21
  • 23
1

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

ejk314
  • 381
  • 1
  • 11
  • All methods representing py.test tests start with `test`. What I need here is py.test API, because py.test has collected all the tests beforehand (basically the way you suggested here) and already knows which test is to be run now. I just don't know the right question to ask py.test :-) – Dr. Jan-Philip Gehrcke Jul 18 '13 at 15:08
  • Comment after your edit: I don't want to simply have a list of all the methods starting with "test_". From this list, I am after the one name / the method of the test being executed *right now*. And this only the py.test internals can tell. – Dr. Jan-Philip Gehrcke Jul 18 '13 at 15:13
  • I think you want `setup_method` instead of just `setup` – ejk314 Jul 18 '13 at 15:17
  • I want `setup`, because this is what py.test calls for initializing each test. – Dr. Jan-Philip Gehrcke Jul 18 '13 at 15:33
  • Thanks, that was definitely the right pointer. I was using py.test with `setup` and `teardown` for a long time now without realizing that those methods are more or less supported only for legacy reasons (this is right, isn't it?). In order to answer the question with a code example, I have submitted my own answer. I hope this is fine with you. – Dr. Jan-Philip Gehrcke Jul 19 '13 at 10:52
1

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

Delimitry
  • 2,987
  • 4
  • 30
  • 39
Robert Caspary
  • 1,584
  • 9
  • 7
1

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
matt3o
  • 615
  • 1
  • 5
  • 17
-1

Try type(self).__name__ perhaps?

kindall
  • 178,883
  • 35
  • 278
  • 309