簡體   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