Python中不盡如人意的斷言Assertion

Python Assert 爲什麼不盡如人意

Python中的斷言用起來很是簡單,你能夠在assert後面跟上任意判斷條件,若是斷言失敗則會拋出異常。html

>>> assert 1 + 1 == 2
>>> assert isinstance('Hello', str)
>>> assert isinstance('Hello', int)

Traceback (most recent call last):
  File "<input>", line 1, in <module>
AssertionError

其實assert看上去不錯,然而用起來並不爽。就好比有人告訴你程序錯了,可是不告訴哪裏錯了。不少時候這樣的assert還不如不寫,寫了我就想罵娘。直接拋一個異常來得更痛快一些。python

改進方案 #1

一個稍微改進一丟丟的方案就是把必要的信息也放到assert語句後面,好比這樣。git

>>> s = "nothin is impossible."
>>> key = "nothing"
>>> assert key in s, "Key: '{}' is not in Target: '{}'".format(key, s)

Traceback (most recent call last):
  File "<input>", line 1, in <module>
AssertionError: Key: 'nothing' is not in Target: 'nothin is impossible.'

看上去還行吧,可是其實寫的很蛋疼。假如你是一名測試汪,有成千上萬的測試案例須要作斷言作驗證,相信你面對以上作法,心中必定有千萬只那種馬奔騰而過。github

改進方案 #2

無論你是你是搞測試仍是開發的,想必聽過很多測試框架。你猜到我要說什麼了吧?對,不用測試框架裏的斷言機制,你是否是灑。shell

py.test

py.test 是一個輕量級的測試框架,因此它壓根就沒寫本身的斷言系統,可是它對Python自帶的斷言作了強化處理,若是斷言失敗,那麼框架自己會盡量多地提供斷言失敗的緣由。那麼也就意味着,用py.test實現測試,你一行代碼都不用改。框架

import pytest

def test_case():
    expected = "Hello"
    actual = "hello"
    assert expected == actual

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

"""
================================== FAILURES ===================================
__________________________________ test_case __________________________________

    def test_case():
        expected = "Hello"
        actual = "hello"
>       assert expected == actual
E       assert 'Hello' == 'hello'
E         - Hello
E         ? ^
E         + hello
E         ? ^

assertion_in_python.py:7: AssertionError
========================== 1 failed in 0.05 seconds ===========================
""""

unittest

Python自帶的unittest單元測試框架就有了本身的斷言方法self.assertXXX(),並且不推薦使用assert XXX語句。單元測試

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FoO')

if __name__ == '__main__':
    unittest.main()
    
"""
Failure
Expected :'FOO'
Actual   :'FoO'

Traceback (most recent call last):
  File "assertion_in_python.py", line 6, in test_upper
    self.assertEqual('foo'.upper(), 'FoO')
AssertionError: 'FOO' != 'FoO'
"""

ptest

我很是喜歡ptest,感謝Karl大神寫了這麼一個測試框架。ptest中的斷言可讀性很好,並且智能提示也很方便你經過IDE輕鬆完成各類斷言語句。測試

from ptest.decorator import *
from ptest.assertion import *

@TestClass()
class TestCases:
    @Test()
    def test1(self):
        actual = 'foo'
        expected = 'bar'
        assert_that(expected).is_equal_to(actual)

"""
Start to run following 1 tests:
------------------------------
...
[demo.assertion_in_python.TestCases.test1@Test] Failed with following message:
...
AssertionError: Unexpectedly that the str <bar> is not equal to str <foo>.
"""

改進方案 #3

不只僅是你和我對Python中的斷言表示不知足,因此你們都爭相發明本身的assert包。在這裏我強烈推薦assertpy 這個包,它異常強大並且好評如潮。spa

pip install assertpy

看例子:調試

from assertpy import assert_that

def test_something():
    assert_that(1 + 2).is_equal_to(3)
    assert_that('foobar')\
        .is_length(6)\
        .starts_with('foo')\
        .ends_with('bar')
    assert_that(['a', 'b', 'c'])\
        .contains('a')\
        .does_not_contain('x')

從它的github 主頁 文檔上你會發現它支持了幾乎你能想到的全部測試場景,包括但不限於如下列表。

  • Strings

  • Numbers

  • Lists

  • Tuples

  • Dicts

  • Sets

  • Booleans

  • Dates

  • Files

  • Objects

並且它的斷言信息簡潔明瞭,很少很多。

Expected <foo> to be of length <4>, but was <3>.
Expected <foo> to be empty string, but was not.
Expected <False>, but was not.
Expected <foo> to contain only digits, but did not.
Expected <123> to contain only alphabetic chars, but did not.
Expected <foo> to contain only uppercase chars, but did not.
Expected <FOO> to contain only lowercase chars, but did not.
Expected <foo> to be equal to <bar>, but was not.
Expected <foo> to be not equal to <foo>, but was.
Expected <foo> to be case-insensitive equal to <BAR>, but was not.

在發現assertpy以前我也想寫一個相似的包,儘量通用一些。可是如今,我爲毛要從新去造輪子?徹底不必!

總結

斷言在軟件系統中有很是重要的做用,寫的好可讓你的系統更穩定,也可讓你有更多真正面對對象的時間,而不是在調試代碼。

Python中默認的斷言語句其實還有一個做用,若是你寫了一個類型相關的斷言,IDE會把這個對象當成這種類型,這時候智能提示就有如神助。

要不要把內置的斷言語句換成可讀性更好功能更強大的第三方斷言,徹底取決於實際狀況。好比你真的須要驗證某個東西而且很關心驗證結果,那麼必須不能用簡單的assert;若是你只是擔憂某個點可能有坑或者讓IDE認識某個對象,用內置的assert既簡單又方便。

因此說,項目經驗仍是蠻重要的。

關於做者:Python技術愛好者,目前從事測試開發相關工做,轉載請註明原文出處。

歡迎關注個人博客 https://betacat.online,你能夠到個人公衆號中去當吃瓜羣衆。

Betacat.online

相關文章
相關標籤/搜索