Python unittest - trái ngược với assertRaises?


374

Tôi muốn viết một bài kiểm tra để xác định rằng Ngoại lệ không được nêu ra trong một hoàn cảnh nhất định.

Thật đơn giản để kiểm tra nếu Ngoại lệ được đưa ra ...

sInvalidPath=AlwaysSuppliesAnInvalidPath()
self.assertRaises(PathIsNotAValidOne, MyObject, sInvalidPath) 

... nhưng làm thế nào bạn có thể làm điều ngược lại .

Một cái gì đó như thế này tôi là những gì tôi sau ...

sValidPath=AlwaysSuppliesAValidPath()
self.assertNotRaises(PathIsNotAValidOne, MyObject, sValidPath) 

6
Bạn luôn có thể đơn giản làm bất cứ điều gì phải làm trong bài kiểm tra. Nếu nó phát sinh lỗi, điều đó sẽ hiển thị (được tính là lỗi, thay vì lỗi). Tất nhiên, điều đó giả định rằng nó không gây ra bất kỳ lỗi nào, thay vì chỉ là một loại lỗi được xác định. Ngoài ra, tôi đoán bạn phải tự viết.
Thomas K


Thực tế là bạn có thể thực hiện một assertNotRaisesphương thức chia sẻ 90% mã / hành vi của nó với assertRaiseskhoảng ~ 30 dòng mã. Xem câu trả lời của tôi dưới đây để biết chi tiết.
tel

Tôi muốn điều này để tôi có thể so sánh hai hàm với hypothesisđể đảm bảo chúng tạo ra cùng một đầu ra cho tất cả các loại đầu vào, trong khi bỏ qua các trường hợp trong đó bản gốc đưa ra một ngoại lệ. assume(func(a))không hoạt động vì đầu ra có thể là một mảng với giá trị thật mơ hồ. Vì vậy, tôi chỉ muốn gọi một chức năng và nhận Truenếu nó không thất bại. assume(func(a) is not None)tác phẩm tôi đoán
endolith

Câu trả lời:


394
def run_test(self):
    try:
        myFunc()
    except ExceptionType:
        self.fail("myFunc() raised ExceptionType unexpectedly!")

32
@hiwaylon - Không, đây là giải pháp chính xác trong thực tế. Giải pháp được đề xuất bởi user9876 là thiếu sót về mặt khái niệm: nếu bạn kiểm tra việc không nói ValueError, nhưng ValueErrorthay vào đó được nêu ra, bài kiểm tra của bạn phải thoát với điều kiện thất bại, không phải lỗi. Mặt khác, nếu trong việc chạy cùng một mã bạn sẽ đưa ra một KeyError, đó sẽ là một lỗi, không phải là một lỗi. Trong python - khác với một số ngôn ngữ khác - Các ngoại lệ được sử dụng thường xuyên cho luồng điều khiển, đây là lý do tại sao chúng ta có except <ExceptionName>cú pháp thực sự. Về vấn đề đó, giải pháp của user9876 đơn giản là sai.
mac

@mac - Đây cũng là một giải pháp đúng? stackoverflow.com/a/4711722/6648326
MasterJoe2

Điều này có tác dụng đáng tiếc khi hiển thị <100% bảo hiểm (ngoại trừ sẽ không bao giờ xảy ra) cho các xét nghiệm.
Shay

3
@Shay, IMO bạn phải luôn loại trừ các tệp thử nghiệm khỏi các báo cáo bảo hiểm (vì chúng hầu như luôn chạy 100% theo định nghĩa, bạn sẽ làm tăng các báo cáo một cách giả tạo)
Sốt BBQ gốc vào

@ gốc-bbq-sốt, sẽ không để tôi mở cho các bài kiểm tra vô tình bỏ qua. Ví dụ, lỗi đánh máy trong tên thử nghiệm (ttst_function), cấu hình chạy sai trong pycharm, v.v.?
Shay

67

Xin chào - Tôi muốn viết một bài kiểm tra để xác định rằng Ngoại lệ không được nêu ra trong một hoàn cảnh nhất định.

Đó là giả định mặc định - ngoại lệ không được nêu ra.

Nếu bạn không nói gì khác, đó là giả định trong mỗi bài kiểm tra.

Bạn không cần phải thực sự viết bất kỳ khẳng định nào cho việc đó.


7
@IndradhanushGupta Vâng, câu trả lời được chấp nhận làm cho bài kiểm tra có nhiều pythonic hơn bài kiểm tra này. Rõ ràng là tốt hơn so với ngầm.
0xc0de

17
Không có bình luận nào khác chỉ ra lý do tại sao câu trả lời này sai, mặc dù đó là lý do tương tự mà câu trả lời của user9876 là sai: lỗi và lỗi là những thứ khác nhau trong mã kiểm tra. Nếu chức năng của bạn đưa ra một ngoại lệ trong một bài kiểm tra không khẳng định, khung kiểm tra sẽ coi đó là một lỗi , thay vì không xác nhận.
coredumperror

@CoreDumpError Tôi hiểu sự khác biệt giữa lỗi và lỗi, nhưng điều này có buộc bạn phải bao quanh mọi bài kiểm tra với cấu trúc thử / ngoại lệ không? Hoặc bạn muốn giới thiệu chỉ làm điều đó cho các thử nghiệm nêu rõ một ngoại lệ trong một số điều kiện (về cơ bản có nghĩa là ngoại lệ được mong đợi ).
federicojasson

4
@federicojasson Bạn đã trả lời câu hỏi của chính bạn khá tốt trong câu thứ hai đó. Lỗi so với thất bại trong các bài kiểm tra có thể được mô tả ngắn gọn là "sự cố không mong muốn" so với "hành vi ngoài ý muốn", tương ứng. Bạn muốn các bài kiểm tra của mình hiển thị lỗi khi chức năng của bạn gặp sự cố, nhưng không phải khi một ngoại lệ mà bạn biết nó sẽ đưa ra một số đầu vào nhất định sẽ bị ném khi đưa ra các đầu vào khác nhau.
coredumperror

52

Chỉ cần gọi hàm. Nếu nó đưa ra một ngoại lệ, khung kiểm tra đơn vị sẽ đánh dấu lỗi này là lỗi. Bạn có thể muốn thêm một bình luận, ví dụ:

sValidPath=AlwaysSuppliesAValidPath()
# Check PathIsNotAValidOne not thrown
MyObject(sValidPath)

35
Thất bại và lỗi là khác nhau về mặt khái niệm. Hơn nữa, vì trong python Ngoại lệ được sử dụng thường xuyên cho luồng điều khiển, điều này sẽ rất khó hiểu trong nháy mắt (= không khám phá mã kiểm tra) nếu bạn đã phá vỡ logic hoặc mã của mình ...
mac

1
Thử nghiệm của bạn vượt qua hoặc nó không. Nếu nó không vượt qua, bạn sẽ phải đi sửa nó. Cho dù nó được báo cáo là "lỗi" hay "lỗi" hầu như không liên quan. Có một điểm khác biệt: với câu trả lời của tôi, bạn sẽ thấy dấu vết ngăn xếp để bạn có thể thấy PathIsNotAValidOne được ném ở đâu; với câu trả lời được chấp nhận, bạn sẽ không có thông tin đó nên việc gỡ lỗi sẽ khó hơn. (Giả sử Py2; không chắc Py3 có tốt hơn không).
dùng9876

19
@ user9876 - Không. Các điều kiện thoát kiểm tra là 3 (đạt / nopass / lỗi), không phải 2 như bạn có thể tin nhầm. Sự khác biệt giữa lỗi và thất bại là đáng kể và đối xử với chúng như thể chúng giống nhau chỉ là lập trình kém. Nếu bạn không tin tôi, chỉ cần nhìn xung quanh cách người chạy thử nghiệm hoạt động và cây quyết định nào họ thực hiện các lỗi và lỗi. Một tốt điểm bắt đầu cho python là các xfailtrang trí trong pytest.
mac

4
Tôi đoán nó phụ thuộc vào cách bạn sử dụng các bài kiểm tra đơn vị. Cách nhóm của tôi sử dụng các bài kiểm tra đơn vị, tất cả đều phải vượt qua. (Lập trình Agile, với một máy tích hợp liên tục chạy tất cả các bài kiểm tra đơn vị). Tôi biết rằng trường hợp thử nghiệm có thể báo cáo "vượt qua", "thất bại" hoặc "lỗi". Nhưng ở cấp độ cao, điều thực sự quan trọng với đội của tôi là "tất cả các bài kiểm tra đơn vị có vượt qua không?" (tức là "Jenkins có màu xanh không?"). Vì vậy, đối với nhóm của tôi, không có sự khác biệt thực tế giữa "lỗi" và "lỗi". Bạn có thể có các yêu cầu khác nhau nếu bạn sử dụng các bài kiểm tra đơn vị của mình theo một cách khác.
user9876

1
@ user9876 sự khác biệt giữa 'fail' và 'error' là sự khác biệt giữa "khẳng định của tôi không thành công" và "thử nghiệm của tôi thậm chí không đi đến khẳng định". Điều đó, với tôi, là một sự khác biệt hữu ích trong quá trình sửa chữa các bài kiểm tra, nhưng tôi đoán, như bạn nói, không phải cho tất cả mọi người.
CS

14

Tôi là người đăng ban đầu và tôi đã chấp nhận câu trả lời trên của DGH mà không phải sử dụng nó lần đầu tiên trong mã.

Khi tôi đã sử dụng, tôi nhận ra rằng cần một chút tinh chỉnh để thực sự làm những gì tôi cần (để công bằng với DGH, anh ấy / cô ấy đã nói "hoặc một cái gì đó tương tự"!).

Tôi nghĩ rằng nó đáng để đăng các tinh chỉnh ở đây vì lợi ích của người khác:

    try:
        a = Application("abcdef", "")
    except pySourceAidExceptions.PathIsNotAValidOne:
        pass
    except:
        self.assertTrue(False)

Những gì tôi đã cố gắng làm ở đây là để đảm bảo rằng nếu một nỗ lực được thực hiện để khởi tạo một đối tượng Ứng dụng với một đối số không gian thứ hai thì pySourceAidExceptions.PathIsNotAValidOne sẽ được nêu ra.

Tôi tin rằng việc sử dụng đoạn mã trên (dựa nhiều vào câu trả lời của DGH) sẽ làm được điều đó.


2
Vì bạn đang làm rõ câu hỏi của mình và không trả lời nên bạn đã chỉnh sửa nó (không trả lời nó). Xin vui lòng xem câu trả lời của tôi dưới đây.
hiwaylon

13
Nó dường như hoàn toàn ngược lại với vấn đề ban đầu. self.assertRaises(PathIsNotAValidOne, MyObject, sInvalidPath)nên làm công việc trong trường hợp này.
Antony Hatchkins

8

Bạn có thể xác định assertNotRaisesbằng cách sử dụng lại khoảng 90% việc thực hiện ban đầu assertRaisestrong unittestmô-đun. Với phương pháp này, bạn kết thúc với một assertNotRaisesphương pháp, ngoài điều kiện thất bại đảo ngược của nó, hành xử giống hệt nhau assertRaises.

TLDR và ​​bản demo trực tiếp

Hóa ra thật dễ dàng để thêm một assertNotRaisesphương thức vào unittest.TestCase(tôi mất khoảng 4 lần thời gian để viết câu trả lời này giống như đã làm mã). Đây là bản demo trực tiếp của assertNotRaisesphương thức đang hoạt động . Giống nhưassertRaises , bạn có thể chuyển một cuộc gọi và tranh luận assertNotRaiseshoặc bạn có thể sử dụng nó trong một withtuyên bố. Bản demo trực tiếp bao gồm một trường hợp thử nghiệm chứng minh rằng assertNotRaiseshoạt động như dự định.

Chi tiết

Việc thực hiện assertRaisestrong unittestkhá phức tạp, nhưng với một chút phân lớp thông minh, bạn có thể ghi đè và đảo ngược tình trạng lỗi của nó.

assertRaiseslà một phương thức ngắn về cơ bản chỉ tạo một thể hiện của unittest.case._AssertRaisesContextlớp và trả về nó (xem định nghĩa của nó trong unittest.casemô-đun). Bạn có thể định nghĩa _AssertNotRaisesContextlớp của riêng mình bằng cách phân lớp _AssertRaisesContextvà ghi đè __exit__phương thức của nó :

import traceback
from unittest.case import _AssertRaisesContext

class _AssertNotRaisesContext(_AssertRaisesContext):
    def __exit__(self, exc_type, exc_value, tb):
        if exc_type is not None:
            self.exception = exc_value.with_traceback(None)

            try:
                exc_name = self.expected.__name__
            except AttributeError:
                exc_name = str(self.expected)

            if self.obj_name:
                self._raiseFailure("{} raised by {}".format(exc_name,
                    self.obj_name))
            else:
                self._raiseFailure("{} raised".format(exc_name))

        else:
            traceback.clear_frames(tb)

        return True

Thông thường bạn định nghĩa các lớp trường hợp kiểm tra bằng cách chúng kế thừa từ TestCase. Nếu bạn thay vì kế thừa từ một lớp con MyTestCase:

class MyTestCase(unittest.TestCase):
    def assertNotRaises(self, expected_exception, *args, **kwargs):
        context = _AssertNotRaisesContext(expected_exception, self)
        try:
            return context.handle('assertNotRaises', args, kwargs)
        finally:
            context = None

tất cả các trường hợp thử nghiệm của bạn bây giờ sẽ có assertNotRaisesphương thức có sẵn cho họ.


Trường hợp của bạn tracebacktrong elsetuyên bố của bạn đến từ đâu?
NOh

1
@NOhs Có một mất tích import. Nó đã được sửa
tel

2
def _assertNotRaises(self, exception, obj, attr):                                                                                                                              
     try:                                                                                                                                                                       
         result = getattr(obj, attr)                                                                                                                                            
         if hasattr(result, '__call__'):                                                                                                                                        
             result()                                                                                                                                                           
     except Exception as e:                                                                                                                                                     
         if isinstance(e, exception):                                                                                                                                           
            raise AssertionError('{}.{} raises {}.'.format(obj, attr, exception)) 

có thể được sửa đổi nếu bạn cần chấp nhận tham số.

gọi như

self._assertNotRaises(IndexError, array, 'sort')

1

Tôi đã tìm thấy nó hữu ích để vá khỉ unittestnhư sau:

def assertMayRaise(self, exception, expr):
  if exception is None:
    try:
      expr()
    except:
      info = sys.exc_info()
      self.fail('%s raised' % repr(info[0]))
  else:
    self.assertRaises(exception, expr)

unittest.TestCase.assertMayRaise = assertMayRaise

Điều này làm rõ ý định khi kiểm tra sự vắng mặt của một ngoại lệ:

self.assertMayRaise(None, does_not_raise)

Điều này cũng đơn giản hóa việc kiểm tra trong một vòng lặp, điều mà tôi thường thấy mình đang làm:

# ValueError is raised only for op(x,x), op(y,y) and op(z,z).
for i,(a,b) in enumerate(itertools.product([x,y,z], [x,y,z])):
  self.assertMayRaise(None if i%4 else ValueError, lambda: op(a, b))

Một miếng vá khỉ là gì?
ScottMcC

1
Xem en.wikipedia.org/wiki/Monkey_patch . Sau khi thêm assertMayRaisevào, unittest.TestSuitebạn có thể giả vờ đó là một phần của unittestthư viện.
AndyJost 16/03/18

0

Nếu bạn chuyển một lớp Exception cho assertRaises(), một trình quản lý bối cảnh được cung cấp. Điều này có thể cải thiện khả năng đọc các bài kiểm tra của bạn:

# raise exception if Application created with bad data
with self.assertRaises(pySourceAidExceptions.PathIsNotAValidOne):
    application = Application("abcdef", "")

Điều này cho phép bạn kiểm tra các trường hợp lỗi trong mã của bạn.

Trong trường hợp này, bạn đang kiểm tra mức PathIsNotAValidOnetăng khi bạn chuyển các tham số không hợp lệ cho hàm tạo Ứng dụng.


1
Không, điều này sẽ chỉ thất bại nếu ngoại lệ không được nêu ra trong khối trình quản lý bối cảnh. Có thể dễ dàng kiểm tra bằng 'với self.assertRaises (TypeError): nâng TypeError', vượt qua.
Matthew Trevor

@MatthewTrevor Gọi tốt. Như tôi nhớ lại, thay vì kiểm tra mã thực thi chính xác, tức là không tăng, tôi đã đề xuất các trường hợp lỗi kiểm tra. Tôi đã chỉnh sửa câu trả lời tương ứng. Hy vọng tôi có thể kiếm được +1 để thoát khỏi màu đỏ. :)
hiwaylon

Lưu ý, đây cũng là Python 2.7 và sau này: docs.python.org/2/library/...
qneill

0

bạn có thể thử như thế thử: self.assertRaises (Không có, hàm, arg1, arg2) ngoại trừ: vượt qua nếu bạn không đặt mã bên trong khối thử, nó sẽ thông qua ngoại lệ 'AssertsError: Không được nêu ra "và trường hợp thử nghiệm sẽ thất bại. nếu đặt bên trong khối thử đó là hành vi dự kiến.


0

Một cách đơn giản để đảm bảo đối tượng được khởi tạo mà không có bất kỳ lỗi nào là kiểm tra thể hiện loại của đối tượng.

Đây là một ví dụ :

p = SomeClass(param1=_param1_value)
self.assertTrue(isinstance(p, SomeClass))
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.