繁体   English   中英

Python中的单元测试有最小的样式吗?

[英]Is there a minimal style for unittests in Python?

我想知道人们使用什么技术来简化用于单元测试的代码的“大小”。 例如,我试图封送该类的对象并测试封送的对象(但这假定封送工作正常)。

考虑上课

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 ]

我回应上面的评论,即您不应在要测试的实际类上使用测试方法。 test_marshal这样的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)

有关上面使用的Apply语法的说明,请参见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__属性,如此处所述

对于测试初始化​​,建议不要通过调用marshal()函数进行测试。 然后,您不仅必须解析出哪个初始化失败,还必须弄清楚是初始化失败还是编组函数本身。 我建议单元测试的“最小样式”是在可行的情况下尽可能缩小您在任何测试点进行测试的重点。

如果我真的必须测试整个字段的初始化,则可以使用与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)

我听到你说:“真是太可惜了,其中有很多代码!” 是的,但是区别在于:

  • assertFieldsEqualassertFieldEqual是可重用的函数,这些函数已抽象为公共测试用例类,您的其他测试用例可以重用这些类。
  • 故障消息准确描述了正在发生的事情。

当需要测试元帅功能时,只需执行以下操作:

def test_marshal(self):
    expected = '...'
    self.assertEqual(expected, self.nums.marshal())

但是,在比较字符串时,我更喜欢一种方法,该方法可以准确地告诉我字符串在哪里发散。 对于多行字符串,Python 2.7中现在有一个用于该方法的方法,但是过去我为此目的滚动或编写了自己的方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM