Tiếp tục trong phiên bản mới nhất của Python khi một xác nhận không thành công


84

EDIT: chuyển sang một ví dụ tốt hơn và làm rõ lý do tại sao đây là một vấn đề thực sự.

Tôi muốn viết các bài kiểm tra đơn vị bằng Python để tiếp tục thực thi khi một xác nhận không thành công, để tôi có thể thấy nhiều lỗi trong một lần kiểm tra. Ví dụ:

class Car(object):
  def __init__(self, make, model):
    self.make = make
    self.model = make  # Copy and paste error: should be model.
    self.has_seats = True
    self.wheel_count = 3  # Typo: should be 4.

class CarTest(unittest.TestCase):
  def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    self.assertEqual(car.make, make)
    self.assertEqual(car.model, model)  # Failure!
    self.assertTrue(car.has_seats)
    self.assertEqual(car.wheel_count, 4)  # Failure!

Ở đây, mục đích của bài kiểm tra là để đảm bảo rằng Car's __init__đặt các trường của nó một cách chính xác. Tôi có thể chia nó thành bốn phương thức (và đó thường là một ý tưởng tuyệt vời), nhưng trong trường hợp này, tôi nghĩ sẽ dễ đọc hơn nếu giữ nó như một phương thức duy nhất kiểm tra một khái niệm duy nhất ("đối tượng được khởi tạo chính xác").

Nếu chúng ta cho rằng tốt nhất ở đây là không chia nhỏ phương pháp, thì tôi gặp một vấn đề mới: Tôi không thể thấy tất cả các lỗi cùng một lúc. Khi tôi sửa modellỗi và chạy lại kiểm tra thì wheel_countlỗi xuất hiện. Nó sẽ giúp tôi tiết kiệm thời gian để xem cả hai lỗi khi tôi chạy thử nghiệm lần đầu tiên.

Để so sánh, khung kiểm tra đơn vị C ++ của Google phân biệt giữaEXPECT_* xác nhận không nghiêm trọng và ASSERT_*xác nhận nghiêm trọng :

Các khẳng định đi theo từng cặp kiểm tra cùng một điều nhưng có tác động khác nhau đến chức năng hiện tại. Các phiên bản ASSERT_ * tạo ra các lỗi nghiêm trọng khi chúng bị lỗi và hủy bỏ chức năng hiện tại. Phiên bản EXPECT_ * tạo ra lỗi không phải chất béo, không hủy bỏ chức năng hiện tại. EXPECT_ * thường được ưu tiên hơn, vì chúng cho phép nhiều hơn một lỗi được báo cáo trong một bài kiểm tra. Tuy nhiên, bạn nên sử dụng ASSERT_ * nếu việc tiếp tục khi xác nhận được đề cập không thành công là không hợp lý.

Có cách nào để có được EXPECT_*hành vi giống trong Python unittestkhông? Nếu không unittest, thì có một khung kiểm tra đơn vị Python nào khác hỗ trợ hành vi này không?


Tình cờ, tôi tò mò về việc có bao nhiêu thử nghiệm trong đời thực có thể hưởng lợi từ các xác nhận không gây tử vong, vì vậy tôi đã xem một số ví dụ về mã (đã chỉnh sửa 2014-08-19 để sử dụng mã tìm kiếm thay vì Google Code Search, RIP). Trong số 10 kết quả được chọn ngẫu nhiên từ trang đầu tiên, tất cả đều chứa các thử nghiệm đưa ra nhiều khẳng định độc lập trong cùng một phương pháp thử nghiệm. Tất cả sẽ được hưởng lợi từ các khẳng định không gây tử vong.


2
Cuối cùng bạn đã làm gì? Tôi quan tâm đến chủ đề này (vì những lý do hoàn toàn khác mà tôi rất vui được thảo luận ở một nơi rộng rãi hơn là một bình luận) và muốn biết kinh nghiệm của bạn. Nhân tiện, liên kết "ví dụ mã" kết thúc bằng "Rất tiếc, dịch vụ này đã bị đóng", vì vậy nếu bạn có phiên bản đã lưu trong bộ nhớ cache của nó, tôi cũng muốn xem nó.
Davide

Để tham khảo trong tương lai, tôi tin rằng đây là tìm kiếm tương đương trên hệ thống hiện tại, nhưng kết quả không còn như mô tả ở trên.
ZAD-Man

2
@Davide, tôi đã không làm gì cả. Cách tiếp cận "chỉ đưa ra một khẳng định cho mỗi phương pháp" có vẻ quá cứng nhắc giáo điều đối với tôi, nhưng giải pháp duy nhất có thể thực hiện được (và có thể duy trì) dường như là gợi ý "bắt và nối" của Anthony. Tuy nhiên, điều đó quá tệ đối với tôi, vì vậy tôi chỉ mắc kẹt với nhiều xác nhận cho mỗi phương pháp và tôi sẽ phải chạy thử nghiệm nhiều lần hơn mức cần thiết để tìm ra tất cả các lỗi.
Bruce Christensen

Khung thử nghiệm python được gọi là PyTest khá trực quan và theo mặc định hiển thị tất cả các lỗi xác nhận. Đó có thể là một giải pháp xoay quanh vấn đề bạn đang gặp phải.
Surya Shekhar Chakraborty

Câu trả lời:


9

Những gì bạn có thể muốn làm là lấy ra unittest.TestCasevì đó là lớp ném khi một xác nhận không thành công. Bạn sẽ phải kiến ​​trúc lại của mình TestCaseđể không ném (có thể giữ một danh sách các lỗi thay thế). Sắp xếp lại nội dung có thể gây ra các vấn đề khác mà bạn sẽ phải giải quyết. Ví dụ: cuối cùng bạn có thể cần phải bắt nguồn TestSuiteđể thực hiện các thay đổi nhằm hỗ trợ các thay đổi được thực hiện cho của bạn TestCase.


1
Tôi nghĩ rằng đây có lẽ sẽ là câu trả lời cuối cùng, nhưng tôi muốn kiểm tra căn cứ của mình và xem liệu tôi có thiếu gì không. Cảm ơn!
Bruce Christensen

4
Tôi muốn nói rằng việc ghi đè là quá mức cần thiết TestCasevì lợi ích của việc triển khai các xác nhận mềm - chúng đặc biệt dễ thực hiện trong python: chỉ cần bắt tất cả các AssertionErrors của bạn (có thể trong một vòng lặp đơn giản) và lưu trữ chúng trong một danh sách hoặc một tập hợp , sau đó thất bại tất cả chúng cùng một lúc. Hãy xem câu trả lời của @Anthony Batcosystem để biết chi tiết cụ thể.
dcsordas

2
@dscordas Phụ thuộc vào việc điều này dành cho thử nghiệm một lần hay bạn muốn có khả năng này cho hầu hết các thử nghiệm.
dietbuddha

43

Một cách khác để có các xác nhận không nghiêm trọng là nắm bắt ngoại lệ xác nhận và lưu trữ các ngoại lệ trong danh sách. Sau đó, khẳng định rằng danh sách đó trống như một phần của dropsDown.

import unittest

class Car(object):
  def __init__(self, make, model):
    self.make = make
    self.model = make  # Copy and paste error: should be model.
    self.has_seats = True
    self.wheel_count = 3  # Typo: should be 4.

class CarTest(unittest.TestCase):
  def setUp(self):
    self.verificationErrors = []

  def tearDown(self):
    self.assertEqual([], self.verificationErrors)

  def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    try: self.assertEqual(car.make, make)
    except AssertionError, e: self.verificationErrors.append(str(e))
    try: self.assertEqual(car.model, model)  # Failure!
    except AssertionError, e: self.verificationErrors.append(str(e))
    try: self.assertTrue(car.has_seats)
    except AssertionError, e: self.verificationErrors.append(str(e))
    try: self.assertEqual(car.wheel_count, 4)  # Failure!
    except AssertionError, e: self.verificationErrors.append(str(e))

if __name__ == "__main__":
    unittest.main()

2
Khá chắc chắn tôi đồng ý với bạn. Đó là cách Selenium xử lý các lỗi xác minh trong phần phụ trợ python.
Anthony Batcosterone

Ya, vấn đề với giải pháp này là tất cả các xác nhận đều được tính là lỗi (không phải lỗi) và cách hiển thị lỗi không thực sự hữu dụng. Dù sao là một cách và render chức năng có thể được cải thiện easly
eMarine

Tôi đang sử dụng giải pháp này kết hợp với câu trả lời của dietbudda bằng cách ghi đè tất cả các xác nhận unittest.TestCasebằng các khối thử / ngoại trừ.
thodic

Đối với các mẫu thử nghiệm phức tạp, đây là giải pháp tốt nhất để loại bỏ lỗi đơn nhất, nhưng nó làm cho bài kiểm tra trông khá xấu với tất cả các lần thử / ngoại lệ. nó là sự đánh đổi giữa rất nhiều bài kiểm tra và một bài kiểm tra phức tạp. Thay vào đó, tôi đã bắt đầu trả lại một lỗi dict. Vì vậy, tôi có thể kiểm tra toàn bộ testpattern trong một lần kiểm tra và duy trì khả năng đọc cho các nhà phát triển python bình thường đồng nghiệp của tôi.
MortenB

Điều này cực kỳ thông minh, vì vậy bạn sẽ phải ngả mũ.
Courtimas

30

Một tùy chọn được xác nhận trên tất cả các giá trị cùng một lúc dưới dạng một bộ giá trị.

Ví dụ:

class CarTest(unittest.TestCase):
  def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    self.assertEqual(
            (car.make, car.model, car.has_seats, car.wheel_count),
            (make, model, True, 4))

Kết quả từ các bài kiểm tra này sẽ là:

======================================================================
FAIL: test_init (test.CarTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\temp\py_mult_assert\test.py", line 17, in test_init
    (make, model, True, 4))
AssertionError: Tuples differ: ('Ford', 'Ford', True, 3) != ('Ford', 'Model T', True, 4)

First differing element 1:
Ford
Model T

- ('Ford', 'Ford', True, 3)
?           ^ -          ^

+ ('Ford', 'Model T', True, 4)
?           ^  ++++         ^

Điều này cho thấy cả mô hình và số lượng bánh xe đều không chính xác.


Điều này là thông minh. Giải pháp tốt nhất mà tôi đã tìm thấy cho đến nay.
Chen Ni

7

Việc có nhiều xác nhận trong một bài kiểm tra đơn vị được coi là một mô hình phản đối. Một bài kiểm tra đơn vị được mong đợi chỉ để kiểm tra một thứ. Có lẽ bạn đang thử nghiệm quá nhiều. Cân nhắc chia nhỏ bài kiểm tra này thành nhiều bài kiểm tra. Bằng cách này, bạn có thể đặt tên cho từng bài kiểm tra một cách chính xác.

Tuy nhiên, đôi khi bạn có thể kiểm tra nhiều thứ cùng một lúc. Ví dụ khi bạn đang xác nhận các thuộc tính của cùng một đối tượng. Trong trường hợp đó, trên thực tế bạn đang xác nhận xem đối tượng đó có đúng hay không. Một cách để làm điều này là viết một phương thức trợ giúp tùy chỉnh biết cách xác nhận trên đối tượng đó. Bạn có thể viết phương thức đó theo cách mà nó hiển thị tất cả các thuộc tính không thành công hoặc ví dụ hiển thị trạng thái hoàn chỉnh của đối tượng mong đợi và trạng thái hoàn chỉnh của đối tượng thực khi một xác nhận không thành công.


1
@Bruce: Một khẳng định nên thất bại hoặc thành công. Không bao giờ có cái gì đó ở giữa. Kiểm tra phải đáng tin cậy, dễ đọc và có thể bảo trì. Một khẳng định không thành công mà không thất bại trong bài kiểm tra là một ý tưởng tồi. Nó làm cho các bài kiểm tra của bạn trở nên quá phức tạp (làm giảm khả năng đọc và khả năng bảo trì) và có các bài kiểm tra 'được phép thất bại' khiến bạn dễ dàng bỏ qua chúng, có nghĩa là chúng không đáng tin cậy.
Steven

8
bất kỳ lý do nào khiến phần còn lại của bài kiểm tra không thể chạy và nó vẫn gây tử vong. Tôi nghĩ rằng bạn có thể trì hoãn việc trả lại lỗi ở đâu đó để có lợi cho việc tổng hợp tất cả các lỗi có thể xảy ra.
dietbuddha

5
Tôi nghĩ rằng cả hai chúng ta đều đang nói cùng một điều. Tôi muốn mọi khẳng định không thành công sẽ khiến bài kiểm tra thất bại; chỉ là tôi muốn lỗi xảy ra khi phương thức kiểm tra trả về, thay vì ngay lập tức khi xác nhận được kiểm tra, như @dietbuddha đã đề cập. Điều này sẽ cho phép tất cả các xác nhận trong phương pháp được kiểm tra, do đó tôi có thể thấy (và sửa) tất cả các lỗi trong một lần chụp. Bài kiểm tra vẫn đáng tin cậy, có thể đọc được và có thể bảo trì (thực tế còn hơn thế nữa).
Bruce Christensen

10
Anh ấy không nói rằng bài kiểm tra sẽ không thất bại khi bạn nhấn câu khẳng định, anh ấy nói rằng việc thất bại sẽ không ngăn cản các lần kiểm tra khác. Ví dụ, ngay bây giờ tôi đang kiểm tra rằng các thư mục cụ thể là người dùng, nhóm và có thể ghi khác. Mỗi cái là một khẳng định riêng biệt. Sẽ rất hữu ích nếu biết từ đầu ra thử nghiệm rằng cả ba trường hợp đều không thành công, vì vậy tôi có thể khắc phục chúng bằng một lệnh gọi chmod, thay vì nhận được "Đường dẫn không thể ghi do người dùng", phải chạy thử nghiệm lại để nhận được "Đường dẫn là không thể ghi theo nhóm ", v.v. Mặc dù tôi đoán tôi chỉ cho rằng họ nên được kiểm tra riêng biệt ...
Tim Keating

8
Chỉ vì thư viện được gọi là đơn vị nhất, không có nghĩa là bài kiểm tra đó là một bài kiểm tra đơn vị cô lập. Mô-đun đơn nhất, cũng như pytest và mũi và các mô-đun khác, hoạt động tốt cho các bài kiểm tra hệ thống, kiểm tra tích hợp, v.v. Với một lưu ý là bạn chỉ có thể thất bại một lần. Nó thực sự khó chịu. Tôi thực sự muốn thấy tất cả các hàm khẳng định hoặc thêm một tham số cho phép bạn tiếp tục với một lỗi hoặc sự trùng lặp của các hàm khẳng định được gọi là mong đợiBlah, thực hiện một điều như vậy. Sau đó, sẽ là cách dễ dàng hơn để viết các bài kiểm tra chức năng lớn hơn với đơn nhất.
Okken

7

Kể từ khi Python 3.4 bạn cũng có thể sử dụng phép thử phụ :

def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    with self.subTest(msg='Car.make check'):
        self.assertEqual(car.make, make)
    with self.subTest(msg='Car.model check'):
        self.assertEqual(car.model, model)
    with self.subTest(msg='Car.has_seats check'):
        self.assertTrue(car.has_seats)
    with self.subTest(msg='Car.wheel_count check'):
        self.assertEqual(car.wheel_count, 4)

( msgtham số được sử dụng để dễ dàng xác định thử nghiệm nào không thành công.)

Đầu ra:

======================================================================
FAIL: test_init (__main__.CarTest) [Car.model check]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 23, in test_init
    self.assertEqual(car.model, model)
AssertionError: 'Ford' != 'Model T'
- Ford
+ Model T


======================================================================
FAIL: test_init (__main__.CarTest) [Car.wheel_count check]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 27, in test_init
    self.assertEqual(car.wheel_count, 4)
AssertionError: 3 != 4

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=2)

1
Đây bây giờ sẽ là câu trả lời được chấp nhận vì dễ dàng đưa vào mã hiện có nhất.
Michael Scott Cuthbert

5

Thực hiện mỗi khẳng định trong một phương pháp riêng biệt.

class MathTest(unittest.TestCase):
  def test_addition1(self):
    self.assertEqual(1 + 0, 1)

  def test_addition2(self):
    self.assertEqual(1 + 1, 3)

  def test_addition3(self):
    self.assertEqual(1 + (-1), 0)

  def test_addition4(self):
    self.assertEqaul(-1 + (-1), -1)

5
Tôi nhận ra rằng đó là một giải pháp khả thi, nhưng nó không phải lúc nào cũng thực tế. Tôi đang tìm kiếm thứ gì đó hoạt động mà không phải chia nhỏ một thử nghiệm cố kết trước đây thành một số phương pháp nhỏ.
Bruce Christensen

@Bruce Christensen: Nếu họ gắn kết như vậy thì có lẽ họ tạo thành một câu chuyện? Và sau đó chúng có thể được biến thành các học thuyết, thực sự sẽ tiếp tục ngay cả khi thất bại.
Lennart Regebro

1
Tôi có một tập hợp các bài kiểm tra, giống như sau: 1. tải dữ liệu, 2. xác nhận dữ liệu được tải chính xác, 3. sửa đổi dữ liệu, 4. xác nhận sửa đổi hoạt động chính xác, 5. lưu dữ liệu đã sửa đổi, 6. khẳng định dữ liệu được lưu chính xác. Làm thế nào tôi có thể làm điều đó với phương pháp này? không có ý nghĩa gì khi tải dữ liệu vào setup(), vì đó là một trong những bài kiểm tra. Nhưng nếu tôi đặt mỗi khẳng định vào chức năng riêng của nó, thì tôi phải tải dữ liệu 3 lần, và đó là một sự lãng phí tài nguyên rất lớn. Cách tốt nhất để đối phó với một tình huống như vậy là gì?
naught101

Tốt, các bài kiểm tra kiểm tra một trình tự cụ thể phải theo cùng một phương pháp kiểm tra.
Lennart Regebro

4

Có một gói xác nhận mềm trong PyPI được gọi là gói softestnày sẽ xử lý các yêu cầu của bạn. Nó hoạt động bằng cách thu thập các lỗi, kết hợp dữ liệu theo dõi ngoại lệ và ngăn xếp, đồng thời báo cáo tất cả như một phần của unittestđầu ra thông thường .

Ví dụ, mã này:

import softest

class ExampleTest(softest.TestCase):
    def test_example(self):
        # be sure to pass the assert method object, not a call to it
        self.soft_assert(self.assertEqual, 'Worf', 'wharf', 'Klingon is not ship receptacle')
        # self.soft_assert(self.assertEqual('Worf', 'wharf', 'Klingon is not ship receptacle')) # will not work as desired
        self.soft_assert(self.assertTrue, True)
        self.soft_assert(self.assertTrue, False)

        self.assert_all()

if __name__ == '__main__':
    softest.main()

... tạo ra đầu ra bảng điều khiển này:

======================================================================
FAIL: "test_example" (ExampleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\...\softest_test.py", line 14, in test_example
    self.assert_all()
  File "C:\...\softest\case.py", line 138, in assert_all
    self.fail(''.join(failure_output))
AssertionError: ++++ soft assert failure details follow below ++++

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
The following 2 failures were found in "test_example" (ExampleTest):
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Failure 1 ("test_example" method)
+--------------------------------------------------------------------+
Traceback (most recent call last):
  File "C:\...\softest_test.py", line 10, in test_example
    self.soft_assert(self.assertEqual, 'Worf', 'wharf', 'Klingon is not ship receptacle')
  File "C:\...\softest\case.py", line 84, in soft_assert
    assert_method(*arguments, **keywords)
  File "C:\...\Python\Python36-32\lib\unittest\case.py", line 829, in assertEqual
    assertion_func(first, second, msg=msg)
  File "C:\...\Python\Python36-32\lib\unittest\case.py", line 1203, in assertMultiLineEqual
    self.fail(self._formatMessage(msg, standardMsg))
  File "C:\...\Python\Python36-32\lib\unittest\case.py", line 670, in fail
    raise self.failureException(msg)
AssertionError: 'Worf' != 'wharf'
- Worf
+ wharf
 : Klingon is not ship receptacle

+--------------------------------------------------------------------+
Failure 2 ("test_example" method)
+--------------------------------------------------------------------+
Traceback (most recent call last):
  File "C:\...\softest_test.py", line 12, in test_example
    self.soft_assert(self.assertTrue, False)
  File "C:\...\softest\case.py", line 84, in soft_assert
    assert_method(*arguments, **keywords)
  File "C:\...\Python\Python36-32\lib\unittest\case.py", line 682, in assertTrue
    raise self.failureException(msg)
AssertionError: False is not true


----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

LƯU Ý : Tôi đã tạo và duy trìsoftest .


3

mong đợi là rất hữu ích trong gtest. Đây là cách python trong ý chính và mã:

import sys
import unittest


class TestCase(unittest.TestCase):
    def run(self, result=None):
        if result is None:
            self.result = self.defaultTestResult()
        else:
            self.result = result

        return unittest.TestCase.run(self, result)

    def expect(self, val, msg=None):
        '''
        Like TestCase.assert_, but doesn't halt the test.
        '''
        try:
            self.assert_(val, msg)
        except:
            self.result.addFailure(self, sys.exc_info())

    def expectEqual(self, first, second, msg=None):
        try:
            self.failUnlessEqual(first, second, msg)
        except:
            self.result.addFailure(self, sys.exc_info())

    expect_equal = expectEqual

    assert_equal = unittest.TestCase.assertEqual
    assert_raises = unittest.TestCase.assertRaises


test_main = unittest.main

2

Tôi thích cách tiếp cận của @ Anthony-Batcosystem, để nắm bắt ngoại lệ AssertionError. Nhưng một biến thể nhỏ đối với cách tiếp cận này bằng cách sử dụng trình trang trí và cũng là một cách để báo cáo các trường hợp kiểm tra đạt / không đạt.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import unittest

class UTReporter(object):
    '''
    The UT Report class keeps track of tests cases
    that have been executed.
    '''
    def __init__(self):
        self.testcases = []
        print "init called"

    def add_testcase(self, testcase):
        self.testcases.append(testcase)

    def display_report(self):
        for tc in self.testcases:
            msg = "=============================" + "\n" + \
                "Name: " + tc['name'] + "\n" + \
                "Description: " + str(tc['description']) + "\n" + \
                "Status: " + tc['status'] + "\n"
            print msg

reporter = UTReporter()

def assert_capture(*args, **kwargs):
    '''
    The Decorator defines the override behavior.
    unit test functions decorated with this decorator, will ignore
    the Unittest AssertionError. Instead they will log the test case
    to the UTReporter.
    '''
    def assert_decorator(func):
        def inner(*args, **kwargs):
            tc = {}
            tc['name'] = func.__name__
            tc['description'] = func.__doc__
            try:
                func(*args, **kwargs)
                tc['status'] = 'pass'
            except AssertionError:
                tc['status'] = 'fail'
            reporter.add_testcase(tc)
        return inner
    return assert_decorator



class DecorateUt(unittest.TestCase):

    @assert_capture()
    def test_basic(self):
        x = 5
        self.assertEqual(x, 4)

    @assert_capture()
    def test_basic_2(self):
        x = 4
        self.assertEqual(x, 4)

def main():
    #unittest.main()
    suite = unittest.TestLoader().loadTestsFromTestCase(DecorateUt)
    unittest.TextTestRunner(verbosity=2).run(suite)

    reporter.display_report()


if __name__ == '__main__':
    main()

Đầu ra từ bảng điều khiển:

(awsenv)$ ./decorators.py 
init called
test_basic (__main__.DecorateUt) ... ok
test_basic_2 (__main__.DecorateUt) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
=============================
Name: test_basic
Description: None
Status: fail

=============================
Name: test_basic_2
Description: None
Status: pass

1

Tôi gặp vấn đề với câu trả lời từ @Anthony Batcosystem vì nó sẽ buộc tôi phải sử dụng try...catchtrong các bài kiểm tra đơn vị của mình. Thay vào đó, tôi đã đóng gói try...catchlogic trong một ghi đè của TestCase.assertEqualphương thức. Đây là mã:

import unittest
import traceback

class AssertionErrorData(object):

    def __init__(self, stacktrace, message):
        super(AssertionErrorData, self).__init__()
        self.stacktrace = stacktrace
        self.message = message

class MultipleAssertionFailures(unittest.TestCase):

    def __init__(self, *args, **kwargs):
        self.verificationErrors = []
        super(MultipleAssertionFailures, self).__init__( *args, **kwargs )

    def tearDown(self):
        super(MultipleAssertionFailures, self).tearDown()

        if self.verificationErrors:
            index = 0
            errors = []

            for error in self.verificationErrors:
                index += 1
                errors.append( "%s\nAssertionError %s: %s" % ( 
                        error.stacktrace, index, error.message ) )

            self.fail( '\n\n' + "\n".join( errors ) )
            self.verificationErrors.clear()

    def assertEqual(self, goal, results, msg=None):

        try:
            super( MultipleAssertionFailures, self ).assertEqual( goal, results, msg )

        except unittest.TestCase.failureException as error:
            goodtraces = self._goodStackTraces()
            self.verificationErrors.append( 
                    AssertionErrorData( "\n".join( goodtraces[:-2] ), error ) )

    def _goodStackTraces(self):
        """
            Get only the relevant part of stacktrace.
        """
        stop = False
        found = False
        goodtraces = []

        # stacktrace = traceback.format_exc()
        # stacktrace = traceback.format_stack()
        stacktrace = traceback.extract_stack()

        # /programming/54499367/how-to-correctly-override-testcase
        for stack in stacktrace:
            filename = stack.filename

            if found and not stop and \
                    not filename.find( 'lib' ) < filename.find( 'unittest' ):
                stop = True

            if not found and filename.find( 'lib' ) < filename.find( 'unittest' ):
                found = True

            if stop and found:
                stackline = '  File "%s", line %s, in %s\n    %s' % ( 
                        stack.filename, stack.lineno, stack.name, stack.line )
                goodtraces.append( stackline )

        return goodtraces

# class DummyTestCase(unittest.TestCase):
class DummyTestCase(MultipleAssertionFailures):

    def setUp(self):
        self.maxDiff = None
        super(DummyTestCase, self).setUp()

    def tearDown(self):
        super(DummyTestCase, self).tearDown()

    def test_function_name(self):
        self.assertEqual( "var", "bar" )
        self.assertEqual( "1937", "511" )

if __name__ == '__main__':
    unittest.main()

Kết quả đầu ra:

F
======================================================================
FAIL: test_function_name (__main__.DummyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\User\Downloads\test.py", line 77, in tearDown
    super(DummyTestCase, self).tearDown()
  File "D:\User\Downloads\test.py", line 29, in tearDown
    self.fail( '\n\n' + "\n\n".join( errors ) )
AssertionError: 

  File "D:\User\Downloads\test.py", line 80, in test_function_name
    self.assertEqual( "var", "bar" )
AssertionError 1: 'var' != 'bar'
- var
? ^
+ bar
? ^
 : 

  File "D:\User\Downloads\test.py", line 81, in test_function_name
    self.assertEqual( "1937", "511" )
AssertionError 2: '1937' != '511'
- 1937
+ 511
 : 

Có thể đăng các giải pháp thay thế khác để ghi lại stacktrace chính xác trên Cách ghi đè chính xác TestCase.assertEqual (), tạo ra stacktrace phù hợp?


0

Tôi không nghĩ có cách nào để làm điều này với PyUnit và sẽ không muốn thấy PyUnit được mở rộng theo cách này.

Tôi thích gắn bó với một khẳng định cho mỗi hàm thử nghiệm ( hoặc cụ thể hơn là khẳng định một khái niệm cho mỗi thử nghiệm ) và sẽ viết lại test_addition()dưới dạng bốn hàm thử nghiệm riêng biệt. Điều này sẽ cung cấp thêm thông tin hữu ích về thất bại, viz :

.FF.
======================================================================
FAIL: test_addition_with_two_negatives (__main__.MathTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_addition.py", line 10, in test_addition_with_two_negatives
    self.assertEqual(-1 + (-1), -1)
AssertionError: -2 != -1

======================================================================
FAIL: test_addition_with_two_positives (__main__.MathTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_addition.py", line 6, in test_addition_with_two_positives
    self.assertEqual(1 + 1, 3)  # Failure!
AssertionError: 2 != 3

----------------------------------------------------------------------
Ran 4 tests in 0.000s

FAILED (failures=2)

Nếu bạn quyết định rằng cách tiếp cận này không dành cho bạn, bạn có thể thấy câu trả lời này hữu ích.

Cập nhật

Có vẻ như bạn đang thử nghiệm hai khái niệm với câu hỏi cập nhật của mình và tôi sẽ chia chúng thành hai bài kiểm tra đơn vị. Đầu tiên là các tham số đang được lưu trữ khi tạo một đối tượng mới. Điều này sẽ có hai xác nhận, một cho makevà một chomodel . Nếu lỗi đầu tiên không thành công, điều đó rõ ràng cần phải được sửa chữa, việc thứ hai đạt hay không thành công là không liên quan ở thời điểm này.

Khái niệm thứ hai đáng nghi vấn hơn ... Bạn đang kiểm tra xem một số giá trị mặc định có được khởi tạo hay không. Tại sao ? Sẽ hữu ích hơn nếu kiểm tra các giá trị này tại thời điểm chúng thực sự được sử dụng (và nếu chúng không được sử dụng, thì tại sao chúng lại ở đó?).

Cả hai thử nghiệm này đều thất bại và cả hai đều nên. Khi tôi thử nghiệm đơn vị, tôi quan tâm đến thất bại hơn là thành công vì đó là nơi tôi cần tập trung.

FF
======================================================================
FAIL: test_creation_defaults (__main__.CarTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_car.py", line 25, in test_creation_defaults
    self.assertEqual(self.car.wheel_count, 4)  # Failure!
AssertionError: 3 != 4

======================================================================
FAIL: test_creation_parameters (__main__.CarTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_car.py", line 20, in test_creation_parameters
    self.assertEqual(self.car.model, self.model)  # Failure!
AssertionError: 'Ford' != 'Model T'

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (failures=2)

Vì vậy, bạn sẽ chia Car.test_init thành bốn chức năng?
Bruce Christensen,

@Bruce Christensen: Tôi có thể sẽ chia nó thành hai. Nhưng ngay cả khi đó, tôi không chắc rằng những khẳng định của bạn là hữu ích. Xem cập nhật để trả lời.
Johnsyweb

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.