我想知道人们使用什么技术来简化用于单元测试的代码的“大小”。例如,我试图编组一个类的对象并测试元帅的对象(但这假设元帅工作正常)。
考虑班级
import unittest
class Nums(object):
def __init__(self, n1_, n2_, n3_):
self.n1, self.n2, self.n3 = n1_, n2_, n3_
def marshal(self):
return "n1 %g, n2 %g, n3 %g"%(self.n1,self.n2,self.n3)
然后是基于编组,基于列表和正常的测试
class NumsTests(unittest.TestCase):
def setUp(self):
self.nu = Nums(10,20,30)
def test_init1(self):
self.assertEquals(self.nu.marshal(),"n1 %g, n2 %g, n3 %g"%(10,20,30))
def test_init2(self):
self.assertEquals([self.nu.n1,self.nu.n2,self.nu.n3],[10,21,31])
def test_init3(self):
self.assertEquals(self.nu.n1,10)
self.assertEquals(self.nu.n2,21)
self.assertEquals(self.nu.n3,31)
给出以下错误(因为,20!= 21和30!= 31,我的测试初始化不好或测试条件错误)
AssertionError: 'n1 10, n2 20, n3 30' != 'n1 10, n2 21, n3 31'
AssertionError: [10, 20, 30] != [10, 21, 31]
AssertionError: 20 != 21
第一个和第二个错误消息很难理解(因为您必须解析字符串或列表)。但是,第三种技术在用于测试复杂对象的代码量方面迅速爆炸。
有没有更好的方法来简化单元测试而不会产生困难的错误消息?并且,不依赖于元帅功能的准确性?
[已将test_marshal
更改为marshal
]
答案 0 :(得分:3)
我回应上面的评论,你不应该对你正在测试的实际类有测试方法。像test_marshal
这样的函数应该放在别处(假设它们确实存在用于测试而不是一般用途),通常在单元测试文件中。但是,暂时搁置一旁,我会做这样的事情
import unittest
class Nums(object):
FORMAT = "n1 %g, n2 %g, n3 %g" # make this a variable for easy testing
def __init__(self, n1, n2, n3):
self.n1, self.n2, self.n3 = n1, n2, n3 # no underscores necessary
def test_marshal(self):
return self.FORMAT % (self.n1, self.n2, self.n3)
class NumsTests(unittest.TestCase):
def setUp(self):
self.nums = [10, 20, 30] # make a param list variable to avoid duplication
self.nu = Nums(*self.nums) # Python "apply" syntax
self.nu_nums = [self.nu.n1, self.nu.n2, self.nu.n3] # we'll use this repeatedly
def test_init1(self):
self.assertEquals(self.nu.test_marshal(), self.nu.FORMAT % self.nums )
def test_init2(self):
self.assertEquals(self.nums, self.nu_nums)
def test_init3(self):
for reference, test in zip(self.nums, self.nu_nums):
self.assertEquals(reference, test)
有关上面使用的应用语法的说明,请参阅http://docs.python.org/library/functions.html#apply。
通过将您正在测试的内容放入变量中,您可以避免重复代码,这似乎是您的主要关注点。
至于令人困惑的错误信息,我想这取决于你觉得你需要多少细节。就个人而言,我的单元测试给了我预期和不存在的代码和值的事实往往使得相当清楚出了什么问题。但是,如果你真的想要一些特别告诉你哪个字段不正确的东西,而只是那些不匹配的值而你想避免代码重复,你可以这样写:
class NumsTests(unittest.TestCase):
def setUp(self):
self.nums = {"n1": 10, "n2": 20, "n3": 30} # use a dict, not a list
self.nu = Nums(**self.nums) # same Python "apply" syntax
# test_init1 and test_init2 omitted for space
def test_init3(self):
for attr,val in self.nums.items():
self.assertEqual([attr, val], [attr, getattr(self.nu, val)])
如果您确实拥有不匹配的值,那么您现在会收到类似
的错误AssertionError: ["n1", 10] != ["n1", 11]
因此您可以一眼就知道哪个字段不匹配,而不必根据值是什么来推断它。这种方法仍然保留了代码扩展问题,因为无论您向Nums
类添加多少参数,test_init3都将保持相同的大小。
请注意,getattr的使用要求您的__init__
参数与num类中的字段具有相同的名称,例如它们必须命名为n1
而不是n1_
等。另一种方法是使用__dict__ attribute, as described here。
答案 1 :(得分:2)
为了测试初始化,我建议通过调用marshal()
函数进行 not 测试。然后你不仅需要解析哪个初始化程序失败了,你还必须弄清楚它是你的初始化失败还是编组函数本身。我建议的单元测试的“最小风格”是尽可能缩小你在任何测试点测试的重点。
如果我真的必须测试一大堆字段的初始化,我可能会像Eli一样重构:
class MyTestCase(unittest.TestCase):
def assertFieldsEqual(self, obj, expectedFieldValDict):
for fieldName, fieldVal in expectedFieldValDict.items():
self.assertFieldEqual(obj, fieldName, fieldVal)
def assertFieldEqual(self, obj, fieldName, expectedFieldVal):
actualFieldVal = getattr(obj, fieldName)
if expectedFieldVal != actualFieldVal:
msg = "Field %r doesn't match: expected %r, actual %r" % \
(fieldName, expectedFieldVal, actualFieldVal)
self.fail(msg)
class NumsTests(MyTestCase):
def setUp(self):
self.initFields = {'n1': 10, 'n2': 20, 'n3': 30}
self.nums = Nums(**initFields)
def test_init(self):
self.assertFieldsEqual(self.nums, self.initFields)
“好悲伤,”我听到你说,“那是很多代码!”好吧,但差异是:
assertFieldsEqual
和assertFieldEqual
是可重用的函数,它们被抽象为一个公共测试用例类,您的其他测试用例可以重用。当测试你的编组功能时,你可以这样做:
def test_marshal(self):
expected = '...'
self.assertEqual(expected, self.nums.marshal())
但是,在比较字符串时,我更喜欢一种能够准确告诉我字符串分歧的方法。对于多行字符串,现在有一种方法可以在Python 2.7中使用,但是我在过去为此目的使用了自己的方法。