- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章python 如何用 Hypothesis 来自动化单元测试由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
高质量的代码离不开单元测试,而设计单元测试的用例往往又比较耗时,而且难以想到一些极端情况,本文讲述如何使用 Hypothesis 来自动化单元测试 。
刷过力扣算法题的同学都知道,有时候觉得代码已经很完善了,一提交才发现很多情况没有考虑到。然后感叹力扣的单元测试真的牛比.
因此,高质量的代码离不开单元测试,如果现在还没有写过单元测试,建议先去学习以下常用的单元测试库[1],只要实践过,才能感受到本文开头提到的那些痛点.
Hypothesis 是一个 Python 库,用于让单元测试编写起来更简单,运行时功能更强大,可以在代码中查找您不会想到的极端情况。它稳定,强大且易于添加到任何现有测试框架中。它的工作原理是让您编写断言每种情况都应该正确的测试,而不仅仅是您偶然想到的那些.
典型的单元测试需要自己写一些测试用例,然后编写测试函数,通过一段代码运行它,然后根据预期结果检查结果.
Hypothesis 有所不同。它是基于属性进行单元测试。它通过生成与您的规范匹配的任意数据并检查在这种情况下程序是否仍然有效。如果找到了一个失败的用例,它将采用该示例并将其测试用例范围缩减缩减为一定尺寸,然后对其进行简化,直到找到一个仍会导致问题的小得多的示例。然后将其保存,后续单元测试时仍会使用这些用例.
现在就让我们看看怎么用吧.
可以通过 pip 安装,也可以通过源代码安装[2],也可以安装一些扩展[3],如下:
1
2
|
pip
install
hypothesis
pip
install
hypothesis[pandas,django]
|
先写一段代码,保存在 mycode.py 中,功能是对字符串进行特定的编码和解码,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
def encode(input_string):
count = 1
prev =
""
lst = []
for
character
in
input_string:
if
character != prev:
if
prev:
entry = (prev, count)
lst.append(entry)
count = 1
prev = character
else
:
count += 1
entry = (character, count)
lst.append(entry)
return
lst
def decode(lst):
q =
""
for
character, count
in
lst:
q += character * count
return
q
|
对这段代码进行单元测试,往往需要写很多测试用例,现在我们使用 hypothesis 来自动为我们测试,编写 test_mycode.py (文件名随意),内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from
hypothesis
import
given
from
mycode
import
decode,encode
from
hypothesis.strategies
import
text
import
unittest
class
TestEncoding(unittest.TestCase):
@given
(text())
def
test_decode_inverts_encode(
self
, s):
self
.assertEqual(decode(encode(s)), s)
if
__name__
=
=
"__main__"
:
unittest.main()
|
可以看出,这里并没有出现具体的测试用例,而是使用来 text 的策略,相当于 hypothesis 自动穷举来可能的情况,也可以看出它很容易可其他测试框架集成,这里是 unittest。现在来运行一下看看效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
(py38env) ➜ tmp python test_mycode.py
Falsifying example: test_decode_inverts_encode(
self
=
<__main__.TestEncoding testMethod
=
test_decode_inverts_encode>, s
=
'',
)
E
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
ERROR: test_decode_inverts_encode (__main__.TestEncoding)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Traceback (most recent call last):
File
"test_mycode.py"
, line
9
,
in
test_decode_inverts_encode
def
test_decode_inverts_encode(
self
, s):
File
"/Users/aaron/py38env/lib/python3.8/site-packages/hypothesis/core.py"
, line
1162
,
in
wrapped_test
raise
the_error_hypothesis_found
File
"test_mycode.py"
, line
10
,
in
test_decode_inverts_encode
self
.assertEqual(decode(encode(s)), s)
File
"/Users/aaron/tmp/mycode.py"
, line
14
,
in
encode
entry
=
(character, count)
UnboundLocalError: local variable
'character'
referenced before assignment
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Ran
1
test
in
0.048s
FAILED (errors
=
1
)
|
这里测试出当字符串为 '' 的时候会抛出 UnboundLocalError 的异常。现在我们来修复这个 bug,然后把所有的测试用例 s 给打印出来,看看它用了哪些测试用例.
encode 函数加入以下代码:
1
2
|
if
not
input_string:
return
[]
|
test_mycode.py 文件打印出测试用例
1
2
3
4
|
@given
(text())
def
test_decode_inverts_encode(
self
, s):
print
(f
"{s=}"
)
self
.assertEqual(decode(encode(s)), s)
|
再次执行:
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
(py38env) ➜ tmp python test_mycode.py
s
=
''
s
=
'1'
s
=
'0'
s
=
'0'
s
=
'0'
s
=
'Ā'
s
=
'\U000cf5e5'
s
=
'0'
s
=
''
s
=
'0'
s
=
'0'
s
=
'E'
s
=
")dù'\x18\U0003deb3¤jd"
s
=
'\U0005bc37\x07\U000537a1ÝÀãiÎ\U000ce9e5\x0b'
s
=
'\U0005bc37\U0005bc37\U000537a1ÝÀãiÎ\U000ce9e5\x0b'
s
=
'\U0005bc37\U000537a1\U000537a1ÝÀãiÎ\U000ce9e5\x0b'
s
=
'À\U000537a1\U000537a1ÝÀãiÎ\U000ce9e5\x0b'
s
=
'\U000965e1\x12\x85&\U000f500aÄÃc'
s
=
'\n\U0004466c\x86Î\x07'
s
=
'Ê\U00063f1e\x01G\x88'
s
=
'ÚV\n'
s
=
'VV\n'
s
=
'\U0008debf湆è'
s
=
'\U0008debf湆è'
s
=
'\U0008debf湆'
s
=
'\U0008debf\U0008debf'
s
=
'\U0008debf\U0008debfó]½àq\x82#\U00015196\U0001c8beg'
s
=
'\U0008debfgó]½àq\x82#\U00015196\U0001c8beg'
s
=
'?'
s
=
'Î'
s
=
'Î\U00085b9e'
s
=
"Î8'?\U00057c38Ù;\x07\U000a5ea8Ò»=\U00091d5b~8뺈"
s
=
'\U000d6497Ý>'
s
=
'\U000e0f01'
s
=
'\U000e0f01Å0y¢KN®'
s
=
'\U000e0f01Å0y¢KN®'
s
=
'\U00050a06'
s
=
'Å\U000b98b3か\U000ba80aá`Ã-Êu\x8c\x90³FÔ"'
s
=
'\x8e\U0004612a\x83ç'
s
=
'\x8e'
s
=
'\x8e\x98\U000fb3e0\U0010d2b3\x10\x82\x94Ð渥'
s
=
'¥W'
s
=
'p\U000e5a2aE·`ì'
s
=
'\U000b80f8\x12\U000c2d54'
s
=
'.\U000703de'
s
=
'6\U00010ffa\U000f7994\x8e'
s
=
'116\U000f7994\x8e'
s
=
'1?6\U000f7994\x8e'
s
=
'4?6\U000f7994\x8e'
s
=
'4\x8e6\U000f7994\x8e'
s
=
'0'
s
=
'\U0006a564´Ð\x93ü\x9eb&i\x1cÑ'
s
=
'\U000ceb6f'
s
=
'\U000ceb6f\xa0\x08'
s
=
'\U000ceb6f\xa0\x08'
s
=
'\U000ceb6fꄃ\x08'
s
=
'\U000ceb6fꄃ匀\U0007cc15\U000b2aaa×**'
s
=
'\U000ceb6fꄃ匀'
s
=
'匀ꄃ匀'
s
=
'J\x14?ö'
s
=
'q)'
s
=
'q)'
s
=
'q\U00060931'
s
=
'q6'
s
=
'\U000e3441'
s
=
'\U000e3441\U00019958¯'
s
=
'\x13'
s
=
'\U000f34dbk'
s
=
'Kp&tÛà'
s
=
'\nö\x93'
s
=
'\n\n\x93'
s
=
'\U00019c8dѳ\U00056cbd\U000e3b2f\U00058d302'
s
=
'\x90=R\x8bß\x03'
s
=
'\x9a'
s
=
'\U000147e7'
s
=
'\U000147e7\x85\U0007a3ef'
s
=
'\U000147e7\U00050a070Â>'
s
=
'\U000a4089\x0eC+RÁ\x02\x97\x9cüÌïSS\U0006cbc5;ÿ~\x16\x019VÇ\U000a32fdQ÷\x15'
s
=
'ÞÚ¾\x19©Z®'
s
=
'ਸ਼æ'
s
=
'\U000cd45a'
s
=
'\U000cd45a\U000e15cbÑ\x08J\ueb3eúß\x07I\x91\x9a\x18\x16Ç\x80\x1a'
s
=
'\x8f}º\x0eq\x0b'
s
=
'\x0e}º\x0eq\x0b'
s
=
"\U000e05a3&¶º[fõ\x8bÜR'ͼt\x97íW\x05\U000caea9\U0008fd74\U000e8f1c¹?dfƾ\x13"
s
=
'\x10\U000e12e2ù\U0006f96erý\U00014baf\x00\x95\U000dbc92É\U00081613µ\U0003b865Z\U0008cc3c'
s
=
'ú\U000b561f\x8fÎ'
s
=
'\tàÖ÷'
s
=
'à\x92©Ì\U000618fa\x92'
s
=
'\U000aaf94\x94\x84\U000cda69\U0005291a\U000a63deþ¿O\x8a>\U000b458bÊ.\U00086f07\x1a'
s
=
'\U0009754e?U_\xa0\x13PQ\x18º\x07\U0006c9c5.Á'
s
=
'\U00102456'
s
=
'³WᵎÕ'
s
=
'\x14\x1c'
s
=
'\x14'
s
=
'\x14\U00105bcd"\x10Ô\x99\U000a5032R\U00056c44V&÷>+\U000aaff2ñ®\U000d7570%ª!\U00032553´8x^«'
s
=
'\x00\U000e2ac4¼ÄUrB'
s
=
'\x00\U000e2ac4¼ÄUrB'
s
=
'\x00\U000e2ac4¼ÄUrB'
s
=
'ª\x1aU\x8aÇ\U000b2fb9\U0005a586'
.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Ran
1
test
in
0.180s
OK
|
从执行结果可以看出,'' 首先被测试,其次 hypothesis 使用了大量的极端测试用例,减轻了手写的负担,大大提升了效率.
虽然 hypothesis 具有自动记忆功能,你仍然可以显式的指定某个测试用例一直被测试,而且这是推荐的做法,比如我想在每次的测试中都测试 '',可以这样写:
1
2
3
4
5
6
7
8
|
from
hypothesis
import
given, example
from
hypothesis.strategies
import
text
@given
(text())
@example
("")
def
test_decode_inverts_encode(s):
assert
decode(encode(s))
=
=
s
|
这一点非常有用,提升了测试代码的可读性,可以用来告诉开发人员或者未来的自己,输入的字符串必须要考虑 '' 的情形.
此外,执行单元测试,不一定要使用 unittest.main(),也可以这样,是不是很方便:
1
2
|
if
__name__
=
=
"__main__"
:
test_decode_inverts_encode()
|
以上仅仅是抛砖引玉,hypothesis 还有很多自动化的特性,不再一一列举,最好的学习方法是边做,边尝试。hypothesis 是一个开源项目,有着详细的官方文档[4],GitHub 仓库[5]这里都是你开启自动化测试的好地方:
[1] 。
库: https://realpython.com/python-testing/ 。
[2] 。
源代码安装: https://github.com/HypothesisWorks/hypothesis/blob/master/CONTRIBUTING.rst 。
[3] 。
扩展: https://hypothesis.readthedocs.io/en/latest/extras.html 。
[4] 。
官方文档: https://hypothesis.readthedocs.io/en/latest/quickstart.html#running-tests 。
[5] 。
GitHub 仓库: https://github.com/HypothesisWorks/hypothesis/ 。
以上就是python 如何用 Hypothesis 来自动化单元测试的详细内容,更多关于python 用 Hypothesis 来自动化单元测试的资料请关注我其它相关文章! 。
原文链接:https://cloud.tencent.com/developer/article/1764139 。
最后此篇关于python 如何用 Hypothesis 来自动化单元测试的文章就讲到这里了,如果你想了解更多关于python 如何用 Hypothesis 来自动化单元测试的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我是一名优秀的程序员,十分优秀!