单元测试

 

单元测试

单元测试的目的并不是查找bug,而是帮助我们更好的设计我们的代码,如何合理的来拆分我们的代码。

单元测试vs集成测试

集成测试检查各个组件间协作运行是否正常,单元测试检查应用程序中的一个某一个小的功能模块。

相关工具

以python为例

  • unittest
  • nose or nose2
  • pytest

其中,nose和nose2基于unittest, 如果使用python2可以使用前两中,pytest要求python3.7+。pytest有较多插件, 显示内容更为丰富一些。

用官方例子比较一下:

# content of test_sample.py
def inc(x):
    return x + 1


def test_answer():
    assert inc(3) == 5

直接运行pytest

[garlic@centos8 pytest]$ pytest
========================================= test session starts =========================================
platform linux -- Python 3.6.8, pytest-7.0.1, pluggy-1.0.0
rootdir: /home/garlic/pytest/pytest
collected 1 item

test_sample.py F                                                                                [100%]

============================================== FAILURES ===============================================
_____________________________________________ test_answer _____________________________________________

    def test_answer():
>       assert inc(3) == 5
E       assert 4 == 5
E        +  where 4 = inc(3)

test_sample.py:7: AssertionError
======================================= short test summary info =======================================
FAILED test_sample.py::test_answer - assert 4 == 5
========================================== 1 failed in 0.03s ==========================================
[garlic@centos8 pytest]$ cat test_sample.py
# content of test_sample.py
def inc(x):
    return x + 1


def test_answer():
    assert inc(3) == 5

如果用unittest要写成下面的样子:

# content of test_sample.py
import unittest

def inc(x):
    return x + 1

class TestInc(unittest.TestCase):

    def test_answer(self):
        assert inc(3) == 5

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

输出结果如下:

[garlic@centos8 pytest]$ python3 test_sample.py
F
======================================================================
FAIL: test_answer (__main__.TestInc)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_sample.py", line 10, in test_answer
    assert inc(3) == 5
AssertionError

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

FAILED (failures=1)

比较而言, pytest编写更加简单,显示结果更加丰富, 更加详细的说明可以参考

  • https://realpython.com/pytest-python-testing/#how-to-install-pytest
  • https://docs.pytest.org/en/latest/

 

一些规则

根据实际情况选择好了工具, 其实重要的是编写测试用例,下面是一些可以参考的单元测试最佳实践:

  • Test only one code unit at a time
  • Don’t make unnecessary assertions
  • Make each test independent to all the others
  • Mock out all external services and state
  • Don’t unit-test configuration setting
  • Use the most appropriate assertion methods
  • Do not print anything out in unit tests

相关更详细的可以参考:

  • http://www.kyleblaney.com/junit-best-practices/
  • https://stackify.com/unit-testing-basics-best-practices/
  • https://dzone.com/articles/unit-testing-best-practices

 

目录布局

项目代码ke’y

project/
│
├── my_app/
│   └── __init__.py
│
└── tests/
    |
    └── unit/
    |   ├── __init__.py
    |   └── test_sum.py
    |
    └── integration/
        |
        ├── fixtures/
        |   ├── test_basic.json
        |   └── test_complex.json
        |
        ├── __init__.py
        └── test_integration.py

分为源码,单元测试unit,集成测试integration,外部依赖数据可以放到fixtures中。如果测试前有相关依赖可以正再setUp方法中, 例子中依赖json文件。

import unittest


class TestBasic(unittest.TestCase):
    def setUp(self):
        # Load test data
        self.app = App(database='fixtures/test_basic.json')

    def test_customer_count(self):
        self.assertEqual(len(self.app.customers), 100)

    def test_existence_of_customer(self):
        customer = self.app.get_customer(id=10)
        self.assertEqual(customer.name, "Org XYZ")
        self.assertEqual(customer.address, "10 Red Road, Reading")


class TestComplexData(unittest.TestCase):
    def setUp(self):
        # load test data
        self.app = App(database='fixtures/test_complex.json')

    def test_customer_count(self):
        self.assertEqual(len(self.app.customers), 10000)

    def test_existence_of_customer(self):
        customer = self.app.get_customer(id=9999)
        self.assertEqual(customer.name, u"バナナ")
        self.assertEqual(customer.address, "10 Red Road, Akihabara, Tokyo")

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

 

自动测试

可以通过一些工具Travis CI 完成自动化测试

language: python
python:
  - "2.7"
  - "3.7"
install:
  - pip install -r requirements.txt
script:
  - python -m unittest discover

如果依赖一些其他package可以在requirements.txt中设置。

 

其他

单元测试有助于针对关键代码块逻辑的验证,可以抛开外部依赖,进行多种异常、正常的输入规则逻辑处理。使得代码更健壮。

本次任务是对之前的系统不是很熟悉, 要在原来的代码中嵌入一些逻辑判断,尝试着做了一下单元测试。使用过程中通过单元测试验证自己代码设计是否符合要求,由于对业务不是很熟悉,在同事测试后还是发现了问题, 使用单元测试对设计问题进行了快速验证,并很方便的执行之前所有单元测试用例,防止影响程序其他处理逻辑。

相比较之前做的中间业务系统,业务比较熟悉,但是系统接口调试居多,涉及到测试代码,会写一些挡板(mock), 开发完成后做下本地组装测试,可以防止集成测试,功能测试出现较大问题。

 

参考及引用

https://realpython.com/python-testing/#automated-vs-manual-testing

图片from陳禮樂

Comments are closed.