在Python unittest中定义一次性类
如果这个问题重复了,请随意关闭,但我还没看到类似的内容。我之前主要用的是“经典面向对象编程”的语言,比如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 个回答
我昨晚解释我想做的事情时,表达得不太清楚(凌晨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
方法没有被简化,而是返回了它能给出的最基本的值,因为这个类是打算被扩展的。现在测试 doStuff
和 defineStuff
是有意义的,因为这两个方法实际上都有实现。
回到我最初想问的问题……正如我在评论中提到的,我并不打算在这个项目中定义子类。(我确实打算在其他项目中这样做,但我想先确认这个项目能按我预期的方式工作,再继续其他项目。)因此,即使 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
的可能方式。我无法覆盖所有情况,也不打算这样做,但我能做的测试用例越多,就越能确保我的逻辑按预期工作。
这让我想到了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
来引用嵌套类。