# 单元测试
Date: 2022-05-08


#### **单元测试**

单元测试的目的并不是查找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[陳禮樂](https://www.facebook.com/groups/296994820448873/user/100004188696949/?__cft__[0]=AZW8PAOM2q1wbR5dG2zX4qr0rdyzKWPyp0NsUXmej0wVT-rIHWoe0lScIUTt3s0mkRtqwG7wbW4WUHe6bcAunCgjkxSYD3dei1_atWrhLMgdYJdxwrnBNMYa8PVC66AluSln4CjEmUoE6DVDUZKKyXAP&__tn__=-UC*F)

