Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ How-to ] 如何写单元测试 #42

Closed
LeoQuote opened this issue Jan 23, 2019 · 3 comments
Closed

[ How-to ] 如何写单元测试 #42

LeoQuote opened this issue Jan 23, 2019 · 3 comments
Labels
document enhancement New feature or request

Comments

@LeoQuote
Copy link
Collaborator

LeoQuote commented Jan 23, 2019

最近学习了TDD, 学会了一些简单的单元测试方法, 跟大家分享一下, 希望大家看完了, 就可以在项目里加几个单元测试, 提高一下项目的覆盖率

单元测试的核心是比起集成测试, 更加关注过程, 而不是关注结果. 而且因为是"单元"测试, 单个的单元测试只关注一个方法甚至一个方法的一部分, 这些测试全部通过了, 整个系统也就通过了

为了达到"单元"这个效果, 写一个方法的单元测试, 我们要做的事情就是如下几步, 这里我拿一个简单的例子做讲解

# code.py
def func_a():
    result = func_b()
    if result == 'success':
        return True
  1. 屏蔽, 模拟这个方法内调用的其它方法, 比如在要测试A方法, A方法中调用了 B方法
  2. 在测试脚本中调用A方法
  3. 验证B方法有没有被调用, 调用的参数对不对, A方法中对返回值的处理正不正确

Python中模拟方法用到的是mock 模块, 里面有patch作为decorator

https://docs.python.org/3.6/library/unittest.mock.html

那么我们的测试脚本可以这么写

# tests.py
from unittest import TestCase
from code import func_a

class FuncATest(TestCase):
    def setUp(self):
        # 这里定义在这个test class下每次运行测试之前要做的事情, 比如模拟数据

    def tearDown(self):
        # 这里定义每次结束测试要做的事情, 比如销毁模拟数据
        # 在写测试的时候, 要保证各个测试是独立运行的, 
        # 在测试中创建的数据就删除,这是比修改数据后改回来更简单有效的策略
   
    @patch('func_b')
    # 这里把func_b 已经模拟成了一个Mock 对象, 你可以在自己的测试代码里定义它的返回值等
    def testA(self, _func_b): # _func_b 代表被mock的func_b 用_func_b 在程序主题里制定 func_b 的返回值等
        _func_b.return_value = 'success'
        a_result = func_a()
        _func_b.assert_called_once()
       # 验证有没有被调用, 还有更多的断言比如 assert_called_once_with() 可以查看(文档)[https://docs.python.org/3.6/library/unittest.mock.html#the-mock-class]
        self.assertTrue(a_result)
        
        # 验证非 success 的返回
        _func_b.return_value = 'some_thing_else'
        a_result = func_a()
        self.assertIsNone(a_result)

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

这样一个简单的单元测试就完成了, 如我所说, patch所有的调用, 只关注于方法内部. 验证方法内部的数据处理, 以及if for之类的判断是否正常

patch还有别的用法, 大家可以参考文档, 这里我再补充一下patch的一个要注意的点

在patch多个方法的时候, 要注意测试函数的参数, 第一个参数是倒数第一个被patch 的方法, 第二个参数是倒数第二个被patch的方法, 这是由于装饰器的机制造成的, 只要在平时的时候多加注意就可以了

@patch('module.ClassName2')
@patch('module.ClassName1')
def test(MockClass1, MockClass2):

另外在patch对象方法的时候, 要注意要这么写

# code.py
class A:
    def some_method(self):
# tests.py

@patch('A')
def testA(_mock_a):# 同样的 _mock_a 代表的是A
    _mock_a.return_value.some_method.return_value = 'mock return value of the method'
    new_a = A()
    new_a.some_method()
    _mock_a.return_value.some_method.assert_called_once()
    # 这里有点绕, 因为 我们是新建了一个A对象, 所以这个方法其实是 A的返回的对象的方法
    # 在设置方法的返回值的时候, 写了两次 return_value

目前测试都在各个文件夹下的tests.py 里, 大家可以参考之前有过的测试, 并且记得一定要看PythonDjango 的文档.

对于django的测试也可以使用Client 进行模拟访问, 这一部分可以参考我们的 注册测试

今天就写到这里, 大家单元测试写起来, 有什么疑问的可以发在这里, 希望我的文字可以给大家一些启发

@hhyo hhyo pinned this issue Jan 25, 2019
@hhyo
Copy link
Owner

hhyo commented Mar 4, 2019

尽量使用with 或者decorator来进行mock管理,不需要手动stop

@stale stale bot added the wontfix This will not be worked on label Apr 16, 2019
@hhyo hhyo added the document label Apr 17, 2019
@stale stale bot removed the wontfix This will not be worked on label Apr 17, 2019
@LeoQuote LeoQuote added the enhancement New feature or request label Apr 17, 2019
Repository owner deleted a comment from stale bot Jun 16, 2019
@hhyo
Copy link
Owner

hhyo commented Jun 16, 2019

@hhyo hhyo unpinned this issue Jul 14, 2019
@hhyo
Copy link
Owner

hhyo commented Jul 4, 2020

感谢大佬的调教

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
document enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants