什么是单元测试? 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。
为什么要写单元测试?
写单元测试似乎是一件很枯燥很乏味的工作,又似乎需要花费一些时间。那我们为什么要写单元测试那?
1 2 3 4 5 1. 通过编写测试代码更能明白你所做应用的目的和意图,没有测试,这些将会变得很模糊。 2. 通过单元测试能使你很容易的发现在引入新代码之后所带来的问题,没有单元测试, 你可能不会发现这些问题,或者在遇到问题后你要花几个小时去寻找问题出在哪。 3. 有测试的代码更受人欢迎。 4. 测试有助于团队合作,测试能够减少同事无意间破坏你的代码的机会。
如何编写测试?
单元测试的根本目的是验证代码行为是否符合预期,所以通常我们需要设计用例,验证在给定输入的情况下是否能得到我们想要的输出
在python中,通常使用自带的unittest或者第三方包nose进行单元测试。这里我们选择使用nose 进行测试。这里选择使用nose的原因是因为nose的使用更简单、快速、灵活,无需编写类。
nose使用:
1 2 3 4 nosetests [options] [test files or directories] 如果不加目录,默认执行当前目录下的所有符合nose条件的用例 如果加目录,则运行指定目录里面符合nose条件的用例
这里符合nose条件指的是当前目录及子目录下名字包含test开头的文件和目录中名字以test开头的方法
1 2 3 4 5 6 7 8 def tearDown(): print "teardown" def test1(): print 1 def test2(): print 2 def setUp(): print "this setup"
1 2 3 4 5 6 7 8 9 10 11 12 13 nosetests -s -v setup testnose.test1 ... 1 ok testnose.test2 ... 2 ok teardown = ------------------------------------------------ Ran 2tests in 0.010s OK
nose优先执行setUp,最后执行tearDown,和函数的位置没关系。在setUp中我们可以创建数据库及一些用的测试数据等准备工作,执行完测试用例后再在tearDown中删除测试数据并关闭连接。
在nose中 我们可以使用一些工具函数来辅助我们验证结果 如:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from nose import toolsdef test_1 () : tools.assert_equal(True , False ) def test_2 () : tools.assert_in(3 , range(5 )) def test_3 () : tools.assert_regexp_matches('<html><div>123</div><html>' , r'<div>(.*?)</div>' ) def test_4 () : tools.assert_regexp_matches('<html><div>123</div><html>' , r'<span>(.*?)</span>' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 $ nosetests -s -v test_sample.py test_sample.test_1 ... FAIL test_sample.test_2 ... ok test_sample.test_3 ... ok test_sample.test_4 ... FAIL ====================================================================== FAIL: test_sample.test_1 ---------------------------------------------------------------------- Traceback (most recent call last): File "/usr/lib/python2.7/site-packages/nose/case.py", line 197, in runTest self.test(*self.arg) File "/home/deploy/test_sample.py", line 6, in test_1 tools.assert_equal(True, False) AssertionError: True != False ====================================================================== FAIL: test_sample.test_4 ---------------------------------------------------------------------- Traceback (most recent call last): File "/usr/lib/python2.7/site-packages/nose/case.py", line 197, in runTest self.test(*self.arg) File "/home/deploy/test_sample.py", line 20, in test_4 tools.assert_regexp_matches('<html><div>123</div><html>', r'<span>(.*?)</span>') AssertionError: Regexp didn't match: '<span>(.*?)</span>' not found in '<html><div>123</div><html>' ---------------------------------------------------------------------- Ran 4 tests in 0.002s FAILED (failures=2)
从结果中可以看到两条不符合预期结果的测试用例没有通过。另外tools下还有还多其他方法供使用,很多从名字上就能看出是如何使用 大家自行参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 assert_almost_equal assert_almost_equals assert_dict_contains_subset assert_dict_equal assert_equal assert_equals assert_false assert_greater assert_greater_equal assert_in assert_is assert_is_instance assert_is_none assert_is_not assert_is_not_none assert_items_equal assert_less assert_less_equal assert_list_equal assert_multi_line_equal assert_not_almost_equal assert_not_almost_equals assert_not_equal assert_not_equals assert_not_in assert_not_is_instance assert_not_regexp_matches assert_raises assert_raises_regexp assert_regexp_matches assert_sequence_equal assert_set_equal assert_true assert_tuple_equal eq_ istest make_decorator nontrivial nontrivial_all nottest ok_ raises set_trace timed trivial trivial_all with_setup
在django里进行单元测试 使用django自带的单元测试框架 具体的用法就不赘述了,这里推荐在使用django时,可以为debug和测试环境下单独创建一个settings文件,这样在测试时可以指定--settings
参数用特定的配置文件进行测试
-k
参数可以保留数据库状态
1 python manage.py test app_name --settings project_name.settings-dev -k
使用nose进行单元测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import osimport jsonimport sysimport djangofrom django.test import Clientfrom nose import toolsclient = Client() def setUp () : try : print "\n---------test start------------" os.environ.setdefault("DJANGO_SETTINGS_MODULE" , "prj_name.settings" ) django.setup() except : sys.exit() def test_nose () : res = client.get('/accounts/test/' ) tools.assert_equal(res, True ) def tearDown () : print "\n---------test accounts end------------"
我们引入Client类, 使用Clinet类可以方便的用来访问django里的链接地址(使用requests也是一个不错的选择)。
在setUp中我们设置下django的配置文件,并启动django,用于Client访问链接
执行测试:
另外, 对于其他的一些返回html的请求或者对于抓取网页内容的爬虫进行测试,个人建议可以验证返回的http状态码即可 或者通过正则匹配的方式验证是否包含我们想要的内容, 如
1 2 3 4 5 6 7 res = client.get('/xxx/xxx/' ) tools.assert_equal(res.status_code, 200 ) tools.assert_regexp_matches(res.content, r'想要验证的regx' )