Python测试驱动开发(TDD)
前言:TDD是一种敏捷开发模式,而不是测试方法。
测试很难 ——- 难在坚持,一直做下去。
现在花时间编写的测试不会立即显出功效,要等到很久以后才有作用 ——- 或许几个月之后避免在重构过程中引入问题,或者升级依赖时捕获回归异常。或许测试会从一种很难衡量的方式回报你,促使你写出设计更好的代码,但你却误以为没有测试也能写出如此优雅的代码。
项目github地址
https://github.com/Tyrone-Zhao/Test-Driven-Development
目录
单元测试的好处
编程就像从井里打水
编程其实很难,我们的成功往往得益于自己的聪明才智。假如我们不那么聪明,TDD就能助我们一臂之力。Kent Beck(TDD理念基本就是他发明的)打了个比方。试想你用绳子从井里提一桶水,如果井不太深,而且桶不是很满,提起来很容易。就算提满满一桶水,刚开始也很容易。但要不了多久你就累了。TDD理念好比是一个棘轮,你可以使用它保存当前的进度,休息一会儿,而且能保证进度绝不倒退。这样你就没必要一直那么聪明了。Test All The Things!
细化测试每个函数的好处
程序变复杂后问题就来了,到时你就知道测试的重要性了。你要面临的危险是,复杂性逐渐靠近,而你可能没发觉,但不久之后你就会变成温水煮青蛙。
首先,写测试很简单,写起来不会花很长时间,所以,别抱怨,只管写就是了。
其次,占位测试很重要。先为简单的函数写好测试,当函数变复杂后,这道心理障碍就容易迈过去。你可能会在函数中添加一个if语句,几周后再添加一个for循环,不知不觉间就将其变成一个基于元类(meta-class)的多态树结构解析器了。因为从一开始你就编写了测试,每次修改都会自然而然地添加新测试,最终得到的是一个测试良好的函数。相反,如果你试图判断函数什么时候才复杂到需要编写测试的话,那就太主观了,而且情况会变得更糟,因为没有占位测试,此时开始编写测试需要投入很多精力,每次改动代码都冒着风险,你开始拖延,很快青蛙就煮熟了。
单元测试与功能测试的区别
单元测试和功能测试之间的界线有时不那么清晰。不过二者之间有个基本区别:功能测试站在用户角度从外部测试应用,单元测试则站在程序员的角度从内部测试应用。
采用的工作流程大致如下:
1)先写功能测试,从用户的角度描述应用的新功能
2)功能测试失败后,想办法编写代码让它通过(或者说至少让当前失败的测试通过)。此时,使用一个或多个单元测试定义希望代码实现的效果,保证为应用中的每一行代码(至少)编写一个单元测试
3)单元测试失败后,编写最少量的应用代码,刚好让单元测试通过。有时,要在第2步和第3步之间多次往复,直到我们觉得功能测试有一点进展为止。
4)然后,再次运行功能测试,看能否通过,或者有没有进展。这一步可能促使我们编写一些新的单元测试和代码等。
由此可以看出,在整个过程中,功能测试站在高层驱动开发,而单元测试从低层驱动我们做些什么。
功能测试代码, 测试主要功能(冒烟测试),数据有效性验证。详细代码可参见上面的github地址中的functional_test/
TDD的重要思想是必要时一次只做一件事。即每次只做必要的操作,让功能测试向前迈出一小步即可。
单元测试代码,视图逻辑测试,数据模型测试,模版表单测试。
Mock,参数校验:
from unittest.mock import patch, call
[...]
@patch("accounts.views.auth")
def test_calls_authenticate_with_uid_from_get_request(self, mock_auth):
self.client.get("/accounts/login?token=abcd123")
self.assertEqual(
mock_auth.authenticate.call_args,
call(uid="abcd123")
)
- 这里的patch mock了accounts.views.auth模块,而且把mock的范围限定在下面的测试函数范围内,测试函数执行完毕,mock即被取消
- mock掉的auth模块中的所有属性和方法也同样是mock对象,通过在参数重传递mock对象为mock_auth来实现mock对象的调用
- self.client.get()方法为django内置的模拟客户端,可以模拟客户端发送请求,示例中请求了”/accounts/login?token=abcd123”的url
- 对应请求url的回调函数为auth模块中的authenticate方法,用mock对象的call_args属性获取其请求的参数,返回一个call(*args, **args)对象
- 下面用mock模块中导入的call方法构建预期结果,使用TestCase类的assertEqual()方法断言结果,至此一个单元测试函数完毕
单元测试与集成测试的区别以及数据库
追求纯粹的人会告诉你,真正的单元测试绝不能设计数据库操作。不仅测试代码,而且还依赖于外部系统,如数据库的测试叫做集成测试更确切。