-1

I have the following project structure:

/root
  /tests
    common_test_case.py
    test_case_1.py
    test_case_2.py
    ...
  project_file.py
  ...

Every test test_case_... is inherited from both unittest.TestCase and common_test_case.CommonTestCase. Class CommonTestCase contains test methods that should be executed by all the tests (though using data unique to each test, stored and accessed in self.something of the test). If some specific tests are needed for an exact test case, they are added directly to that particular class.

Currently I am working on adding logging to my tests. Among other things I would like to log the class the method was run from (since the approach above implies the same test method name for different classes). I would like to stick with the built-in logging module to achieve this.

I have tried the following LogRecord attributes:%(filename)s, %(module)s, %(pathname)s. Though, for methods defined in common_test_case.py they all return path/name to the common_test_case.py and not the test module they were actually run from.

My questions are:

  1. Is there a way to achieve what I am trying to, using only built-in logging module?
  2. Using some third-party/other module (I was thinking maybe some "hacky" solution with inspect)?
  3. Is it possible to achieve (in Python) at all?
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • How is your logger configured? – rdas Apr 11 '19 at 07:40
  • @DroidX86, it is actually a wrapper around the assertion of each test (I did not include the details here to, sort of, "isolate" the question). The code snippet demonstrating the logger configuration is present in [another question of mine](https://stackoverflow.com/q/55625965/8554766). –  Apr 12 '19 at 04:13

2 Answers2

1

Your question appears similar to this one, and solved by:

self.id()

See the function definition here, which calls self.__class__ for the instance of the TestCase class that is instantiated. Given that you are using multiple inheritance the multiple inheritance rules from Python apply:

For most purposes, in the simplest cases, you can think of the search for attributes inherited from a parent class as depth-first, left-to-right, not searching twice in the same class where there is an overlap in the hierarchy.

Which means that common_test_case.CommonTestCase will be searched then unittest.TestCase. If there is no id function in common_test_case.CommonTestCase things should work as if it is only derived from unittest.TestCase. If you feel the need to add an id function to the CommonTestCase, something like this (if really necessary):

def id(self):
  if issubclass(self,unittest.TestCase):
    return super(unittest.TestCase,self).id()
West
  • 722
  • 7
  • 16
  • In my case, test methods are not called directly from test, but rather inherited from another class (see the detailed description in question). That's why this approach will output the class not of test that is currently ran, but of the test the method was defined in (and that is not what I want). Am I missing anything? –  Apr 12 '19 at 06:57
  • Thanks for the explanation in the update. Though it is not the case that `common_test_case.CommonTestCase` is inherited from `unittest.TestCase`. I am not doing so, to prevent `CommonTestCase` tests from running on their own (using recipe from [this answer](https://stackoverflow.com/a/1323554/8554766), actually). –  Apr 15 '19 at 04:46
  • That shouldn't matter unless your common test defines an id function. – West Apr 15 '19 at 06:55
  • Indeed, it works (should have tested is, from the very beginning). `self.id().split('.')[-1]` — gives a test name; `self.id().split('.')[-2]` — gives a class from which test **was called**. And no _hacky_ [`inspect`](https://docs.python.org/3/library/inspect.html) calls!. Thanks for your answer and multiple elaborations. –  Apr 17 '19 at 04:29
0

The solution I've found (that does the trick, so far):

import inspect

class_called_from = inspect.stack()[1][0].f_locals['self'].__class__.__name__

I'm still wondering, though, if there is a "clearer" method, or if this is possible to achieve using logging module.


Recipes, based on West's answer (tested on Python 3.6.1):

test_name = self.id().split('.')[-1]
class_called_from = self.id().split('.')[-2]