在Python unittest中定义一次性类

0 投票
2 回答
1700 浏览
提问于 2025-04-18 09:50

如果这个问题重复了,请随意关闭,但我还没看到类似的内容。我之前主要用的是“经典面向对象编程”的语言,比如Java、C#、C++、PHP(如果你愿意把它算作面向对象语言的话……)等等,现在我正在努力掌握Python。

假设我们有一个这样定义的类:

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))

到目前为止都还不错。不过,我真正想测试的是doStuff这个方法是否正常工作;上面的实现可能太简单了,但我不想在这里贴我的实际代码。因为doStuff依赖于defineStuff来填充stuff这个成员变量,所以测试Foo.doStuff()是没有意义的,除非我想测试基类的实现是否会抛出一个AttributeError(这确实是有效的,但不是我想测试的内容)。不过,我可以做一些类似这样的事情:

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)

我尝试在setUp()方法中定义Bar类,或者在FooTest类中定义一个单独的方法,但无论哪种方式,在我尝试使用它之前都没有定义这个类;我得到了一个NameError异常。我是不是必须在每个测试方法中都定义Bar类,还是我漏掉了什么?Bar类应该是一个测试用的固定类;我不打算在测试之外使用它,但它确实展示了Foo父类的具体功能。我的例子可能有点牵强和过于简单,但我的意思是,我不想为Bar类单独定义一个类文件,因为它只是为了测试而存在。

如果我在FooTest类的定义中定义Bar类,这样可以吗?Python有没有类似Java中的内部类的东西?

2 个回答

0

我昨晚解释我想做的事情时,表达得不太清楚(凌晨4点发问题可能不是个好主意)。根据我例子的展示方式,评论们的确是有道理的。

下面的 Foo 类比我最初的例子要复杂一点:

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)

在这里,defineStuff 方法没有被简化,而是返回了它能给出的最基本的值,因为这个类是打算被扩展的。现在测试 doStuffdefineStuff 是有意义的,因为这两个方法实际上都有实现。

回到我最初想问的问题……正如我在评论中提到的,我并不打算在这个项目中定义子类。(我确实打算在其他项目中这样做,但我想先确认这个项目能按我预期的方式工作,再继续其他项目。)因此,即使 defineStuff 实际上有一个默认值,我仍然不知道更复杂的情况是否能正常工作。

如果一个子类把 defineStuff 定义为返回 [[1,2], [3,4], [5,6]] 呢?我能确定它会正常工作并返回 [[1, 2, 1, 2], [3, 4, 3, 4], [5, 6, 5, 6]] 吗?理论上应该是可以的。这个例子简单到我可以直接看出来它会这样。但我还没有确实测试过,而且我不想单独定义一个子类来测试,因为我根本不会用到它。

关于能否在 unittest.TestCase 派生类的 setUp 方法中定义一个类,并让它被测试方法识别,这个问题还没有定论,但我猜应该是不行的。不过,我意识到我可以这样做:

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)

这样一来,我就不需要为测试定义一个一次性的测试类,但我可以覆盖至少一部分客户端定义 defineStuff 的可能方式。我无法覆盖所有情况,也不打算这样做,但我能做的测试用例越多,就越能确保我的逻辑按预期工作。

2

这让我想到了PHPUnit的模拟对象。我觉得unittest模块不支持这种功能,但可能有一些附加工具可以实现这个功能,这肯定比自己动手做一个解决方案要好,所以我会先去找这样的附加工具。

总之,Python非常灵活,特别是它的类也是对象!所以你可以这样做:

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)

关于你问的嵌套类,我不太确定,试试看吧。不过要记住,Python并没有像其他一些语言那样的隐式作用域(也就是说,在那些语言中this是可选的),所以你总是需要用self.Bar或者FooTest.Bar来引用嵌套类。

撰写回答