对比记录Python几个主流单元测试工具的基本使用示例。

Python中用于单元测试的工具有unittest、pytest、hypothesis、nose等。

unittest是Python自带的模块,pytest和hypothesis是github上star数2k+的人气工具,接下来就是nose。

简单对比下unittest、pytest、hypothesis的基本用法。

用来测试的py文件:

"""
utils.py
Test example
code for testing

Testing is important
Testing is hard: code/isolation/fixtures/indirect value
"""

def to_str(data):
    """Tansform data to a str"""
    if isinstance(data, str):
        return data
    elif isinstance(data, bytes):
        return data.decode('utf-8')
    else:
        raise TypeError('Must supply str or bytes,'
                        'found: %r' % data)


def test_to_str():
    assert to_str('hello') == 'hello'


def encode(input_string):
    if not input_string:
        return []
    count = 1
    prev = ''
    lst = []
    for character in input_string:
        if character != prev:
            if prev:
                entry = (prev, count)
                lst.append(entry)
            count = 1
            prev = character
        else:
            count += 1
    else:
        entry = (character, count)
        lst.append(entry)
    return lst


def decode(lst):
    q = ''
    for character, count in lst:
        q += character * count
    return q

unittest:

# utils_unittest.py
from unittest import TestCase, main

from utils import to_str


class MyUtilsTest(TestCase):
    """Test utils.to_str

    unittest method name must start with 'test'

    unittest doc: https://docs.python.org/3.5/library/unittest.html

    Test methods on TestCase classes must start with the word test.

    It’s important to write both unit tests (for isolated functionality) and
    integration tests (for modules that interact).


    Tests can be numerous, and their set-up can be repetitive. Luckily,
    we can factor out set-up code by implementing a method called setUp(),
    which the testing framework will automatically call for every single test
    we run.

    If setUp() succeeded, tearDown() will be run whether the test method
    succeeded or not.
    """

    def setUp(self):
        self.int_value = 3
        self.str_value = 'foo'

    def tearDown(self):
        print('bye')

    def test_to_str_bytes(self):
        self.assertEqual('hello', to_str(b'hello'))

    def test_to_str_str(self):
        self.assertEqual('foo', to_str(self.str_value))

    def test_to_str_int(self):
        self.assertRaises(TypeError, to_str, 3)

    def test_to_str_bad(self):
        self.assertRaises(TypeError, to_str, object())

    def test_to_str_bad_verbose(self):
        with self.assertRaises(TypeError):
            to_str(self.int_value)

if __name__ == '__main__':
    main()

unittest可以只用用python utils_unittest.py执行,也可以使用pytest运行。(输出可能不同,比如tearDown)

pytest:

# utils_pytest.py
import pytest
from utils import to_str


class TestUtils:
    """Test utils.to_str

    pytest class name must start with 'Test'
    pytest method name must start with 'test'

    pytest doc: https://docs.pytest.org/en/latest/
    https://docs.pytest.org/en/latest/getting-started.html#getstarted

    The pytest framework makes it easy to write small tests,
    yet scales to support complex functional testing for applications
    and libraries.
    """

    def test_to_str_bytes(self):
        assert to_str(b'hello') == 'hello'

    def test_to_str_str(self):
        assert to_str('hello') == 'hello'

    def test_to_str_int(self):
        with pytest.raises(TypeError):
            to_str(3)

运行方式以pytest方式运行。

hypothesis:

# utils_hypothesis.py
from utils import encode, decode

from hypothesis import given, example
import hypothesis.strategies as st


@given(st.text())
@example('')
def test_decode_inverts_encode(s):
    assert decode(encode(s)) == s


"""
从官网复制过来的测试用例
"""

@given(st.integers(), st.integers())
def test_ints_are_commutative(x, y):
    assert x + y == y + x

@given(x=st.integers(), y=st.integers())
def test_ints_cancel(x, y):
    assert (x + y) - y == x

@given(st.lists(st.integers()))
def test_reversing_twice_gives_same_list(xs):
    # This will generate lists of arbitrary length (usually between 0 and
    # 100 elements) whose elements are integers.
    ys = list(xs)
    ys.reverse()
    ys.reverse()
    assert xs == ys

@given(st.tuples(st.booleans(), st.text()))
def test_look_tuples_work_too(t):
    # A tuple is generated as the one you provided, with the corresponding
    # types in those positions.
    assert len(t) == 2
    assert isinstance(t[0], bool)
    assert isinstance(t[1], str)

运行方式可以pytest方式运行,如:

/usr/local/bin/pytest --hypothesis-show-statistics utils_hypothesis.py

总结:

通过简单的示例对比,pytest和hypothesis的用法都挺简洁明了的,hypothesis简洁的同时也最强大,可以方便地提供和处理测试数据等,更加人性化智能化