defining a one-off class within python unittest

老子叫甜甜 提交于 2019-12-13 04:23:59

问题


Feel free to close if this is a duplicate, but I haven't seen anything similar as of yet. I'm from more of a "classical OOP" background (e.g. Java, C#, C++, PHP (if you want to call that an OOP language...) etc), and I'm trying to master Python right now.

Let's say we have a class defined like this:

class Foo:

  def __init__(self):
    self.stuff = self.defineStuff()

  def method1(self, x):
    return x

  def defineStuff(self):
    # this method has no logic in this class; child classes should define this
    pass

  def doStuff(self):
    return self.stuff

class FooTest(unittest.TestCase):
  def testMethod1(self):
    assertEquals(5, Foo().method1(5))

OK, so far so good. However, what I really want to test is that doStuff works properly; the implementation above is probably too trivial for its own good but I'm not posting my actual code here. Since doStuff depends on defineStuff to populate the stuff member variable, testing Foo.doStuff() is meaningless unless I want to test that the base class implementation throws an AttributeError. (Which is valid, but not really what I want to test). I can, however, do something like:

class FooTest(unittest.TestCase):
  # we did this before, it's just here for reference
  def testMethod1(self):
    assertEquals(5, Foo().method1(5))

  def testDefineStuff(self):
    class Bar(Foo):
      def defineStuff(stuff):
        self.stuff = stuff
        return self
    bar = Bar().defineStuff('abcdefg')
    self.assertEquals('abcdefg', bar.stuff)

I've tried defining the Bar class in the setUp() method and as a separate method within the FooTest class, and neither way defines the class before I try to use it; I get a NameError exception. Am I forced to define the Bar class in every test method, or is there something I'm missing? The Bar class is supposed to be a test fixture; I don't intend to ever use it outside of testing, but it does demonstrate the concrete functionality of the Foo parent class. My example is contrived and oversimplified, but my point is, I don't want to have to define a separate class file for the Bar class, since it's intended for testing only.

If I defined the Bar class within the FooTest class definition, would this work? Does Python have the equivalent of internal classes in Java?


回答1:


This reminds me of PHPUnit's mock objects. I believe the unittest module doesn't support them, but maybe there are addons that would give you this feature, which is surely preferable to rolling your own solution, I'd try to find such an addon first.

Anyhow, Python is pretty versatile and in particular its classes are objects, too! Therefore, you can do something like this:

class FooTest(unittest.TestCase):
    def setUp(self):
        class Bar(Foo):
            def defineStuff(stuff):
                self.stuff = stuff
                return self
        # store class locally
        self.Bar = Bar

    def testDefineStuff(self):
        bar = self.Bar().defineStuff('abcdefg')
        self.assertEquals('abcdefg', bar.stuff)

Concerning your question about nested classes, I don't know, just try it. Remember though that the implicit scoping of several other languages (i.e. where this is optional) is not one of Python's features/faults, so you will always have to refer to the nested class as self.Bar or FooTest.Bar.




回答2:


I explained what I was trying to do rather poorly last night (posting questions at 4AM is probably not the smartest idea). The comments were valid given the way my example appeared.

The Foo class below is a bit less trivial than my original example:

class Foo:
  def __init__(self):
    self.stuff = self.defineStuff()

  def defineStuff(self):
    return []

  def doStuff(self):
    try:
      return [x + x for x in self.stuff]
    except TypeError:
      raise FooException("defineStuff must return a list")

class FooTest(unittest.TestCase):
  def testDoStuff(self):
    v = Foo().doStuff()
    self.assertEquals([], v)

So, here the defineStuff method is not stubbed, and instead returns the most basic value it can, since this class is meant to be extended. Now it actually does make sense to test doStuff and even defineStuff since both methods are actually implemented.

Back to what I was attempting to ask with my original question... As I mentioned in the comments, I'm not intending to define child classes within the scope of this project. (I do intend to do so within other projects, but I want to know that this project works as I expect it to before moving on to those.) Thus, even with defineStuff actually defined with a default value, I still don't know if more complex situations will work.

What if a child class defines defineStuff as returning [[1,2], [3,4], [5,6]]? Do I know this will work properly and return [[1, 2, 1, 2], [3, 4, 3, 4], [5, 6, 5, 6]]? In theory, it should. This example in particular is simple enough that I can look at it and know that it will. But I haven't tested it for certain, and I don't want to define a separate child class just to do so since I'm never going to use it.

The jury's still out on whether or not you can define a class within a unittest.TestCase-derived class's setUp method and have it be recognized by your test methods, but I would guess not. However, I realized I can do things along the lines of:

class FooTest(unittest.TestCase):
  @staticmethod
  def sampleStuff(placeholder):
    return [[1, 2], [3, 4], [5, 6]]

  def testDoStuff(self):
    Foo.defineStuff = FooTest.sampleStuff
    v = Foo().doStuff()
    self.assertEquals([[1, 2, 1, 2], [3, 4, 3, 4], [5, 6, 5, 6]], v)

So, this way, I don't need to define a one-off test class for testing, yet I can cover at least a subset of possible ways clients can define defineStuff. I'm not going to be able to cover everything, and I don't intend to, but the more test cases I can make, the more I can be at least fairly sure my logic works as intended.



来源:https://stackoverflow.com/questions/24227829/defining-a-one-off-class-within-python-unittest

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!