2010-06-22 16 views
5

Tôi tự hỏi những kỹ thuật mọi người sử dụng để đơn giản hóa 'kích thước' của mã được sử dụng để kiểm tra đơn vị. Ví dụ tôi đã cố gắng để sắp xếp một đối tượng của lớp và kiểm tra đối tượng marshal'ed (nhưng điều này giả định nguyên soái đang hoạt động chính xác).Có một phong cách tối thiểu cho các unittests bằng Python không?

Hãy xem xét các lớp

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) 

và sau đó marshaling dựa, danh sách dựa, và kiểm tra bình thường

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) 

đó cung cấp cho các lỗi sau đây (kể từ đó, 20! = 21 và 30! = 31 , thử nghiệm của tôi có khởi chạy không tốt hoặc các điều kiện thử nghiệm là sai)

AssertionError: 'n1 10, n2 20, n3 30' != 'n1 10, n2 21, n3 31' 

AssertionError: [10, 20, 30] != [10, 21, 31] 

AssertionError: 20 != 21 

Lần đầu tiên và lần thứ hai thông điệp ror rất khó hiểu (vì bạn phải phân tích chuỗi hoặc danh sách). Tuy nhiên, kỹ thuật thứ 3 nhanh chóng phát nổ trong số lượng mã được sử dụng để kiểm tra các đối tượng phức tạp.

Có cách nào tốt hơn để đơn giản hóa các bài kiểm tra đơn vị mà không tạo thông báo lỗi khó không? Và, không phụ thuộc vào tính xác thực của một chức năng nguyên soái?

[thay đổi test_marshal-marshal]

+0

Bạn không nên có bất kỳ mã thử nghiệm nào trong hệ thống của mình đang được thử nghiệm (trong trường hợp này là lớp 'Nums'). – Skilldrick

+0

@Skilldrick Tôi không biết "mã thử nghiệm trong hệ thống của bạn đang thử nghiệm" nghĩa là gì. –

+1

mã kiểm tra 'Nums' phải nằm trong một tệp riêng biệt từ chính lớp' Nums'. Vì vậy, hãy di chuyển 'test_marshal' sang một tệp khác (tức là tệp thử nghiệm của bạn). –

Trả lời

2

Để khởi tạo thử nghiệm, tôi khuyên bạn nên không thử nghiệm qua gọi hàm marshal(). Không chỉ làm bạn sau đó phải phân tích ra initializer không thành công, bạn cũng phải tìm ra cho dù đó là khởi tạo của bạn đó là thất bại hoặc bản thân chức năng chính. Các "phong cách tối thiểu" cho các bài kiểm tra đơn vị tôi muốn giới thiệu là để thu hẹp trọng tâm của những gì bạn đang thử nghiệm tại bất kỳ điểm thử nghiệm nhiều như là khả thi.

Nếu tôi thực sự đã phải kiểm tra việc khởi tạo một bó toàn bộ các lĩnh vực, tôi có thể cấu trúc lại theo cách tương tự như 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) 

"Good đau buồn," Tôi có thể nghe bạn nói, "đó là một rất nhiều mã! " Vâng vâng, nhưng sự khác biệt là:

  • assertFieldsEqualassertFieldEqual là chức năng tái sử dụng mà đã được trừu tượng hóa để một lớp test chung mà trường hợp thử nghiệm khác của bạn có thể tái sử dụng.
  • Thông báo lỗi mô tả chính xác những gì đang xảy ra.

Khi nói đến thời gian để kiểm tra chức năng soái của bạn, bạn chỉ có thể làm điều này:

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

Khi so sánh chuỗi, tuy nhiên, tôi thích một phương pháp mà nói với tôi chính xác nơi các dây bất đồng. Đối với các chuỗi nhiều dòng, bây giờ có một phương thức cho điều đó trong Python 2.7, nhưng tôi đã cuộn hoặc nôi các phương thức của riêng tôi cho mục đích này trong quá khứ.

3

Tôi xin nhắc lại các ý kiến ​​ở trên mà bạn không nên có phương pháp kiểm tra trên lớp thực tế bạn đang thử nghiệm. Các chức năng như test_marshal nên được đặt ở nơi khác (giả sử chúng tồn tại để thử nghiệm và không phải để sử dụng chung), thường là trong các tệp thử nghiệm đơn vị của bạn. Tuy nhiên, hãy đặt sang một bên cho thời điểm này, tôi sẽ làm điều gì đó như thế này

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) 

Xem http://docs.python.org/library/functions.html#apply để giải thích về cú pháp áp dụng được sử dụng ở trên.

Bằng cách đặt những thứ bạn đang thử nghiệm vào các biến, bạn có thể tránh trùng lặp mã, điều này có vẻ là mối quan tâm chính của bạn.

Đối với các thông báo lỗi gây nhầm lẫn, tôi đoán nó phụ thuộc vào chi tiết bạn cảm thấy mình cần. Cá nhân, thực tế là các bài kiểm tra đơn vị của tôi cung cấp cho tôi dòng mã và giá trị được mong đợi và không có xu hướng làm cho nó khá rõ ràng những gì đã đi sai. Tuy nhiên, nếu bạn thực sự muốn cái gì mà nói với bạn đặc biệt mà lĩnh vực không chính xác, đúng hơn là chỉ các giá trị mà không phù hợp với VÀ bạn muốn tránh trùng lặp mã, bạn có thể viết một cái gì đó như thế này:

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

Nếu bạn từng đã có những giá trị không phù hợp, bây giờ bạn sẽ nhận được lỗi mà trông giống như

AssertionError: ["n1", 10] != ["n1", 11] 

và do đó bạn sẽ biết trong nháy mắt mà lĩnh vực không phù hợp, thay vì phải lý do nó ra dựa trên những gì các giá trị được. Cách tiếp cận này vẫn duy trì vấn đề mở rộng mã, vì test_init3 sẽ giữ nguyên kích thước không có vấn đề bao nhiêu tham số bạn thêm vào lớp Nums của bạn.

Lưu ý rằng việc sử dụng getattr này yêu cầu thông số __init__ của bạn có cùng tên với các trường trong lớp num, ví dụ: chúng phải được đặt tên là n1 thay vì n1_, v.v. Một cách tiếp cận khác sẽ là sử dụng __dict__ attribute, as described here.

+1

Tại sao không đổi tên test_marshal() thành __str __()? Hoặc có thể đổi tên thành __repr __() và đặt định dạng thành "Nums (n1 _ =% g, n2 _ =% g, n3 _ =% g)"? – Oddthinking

+0

Tôi chân thành thứ hai những ý tưởng đó. –

Các vấn đề liên quan