首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

python 如何用 Hypothesis 来自动化单元测试

  • 25-03-08 00:43
  • 3744
  • 14077
blog.csdn.net

高质量的代码离不开单元测试,而设计单元测试的用例往往又比较耗时,而且难以想到一些极端情况,本文讲述如何使用 Hypothesis 来自动化单元测试

刷过力扣算法题的同学都知道,有时候觉得代码已经很完善了,一提交才发现很多情况没有考虑到。然后感叹力扣的单元测试真的牛。

因此,高质量的代码离不开单元测试,如果现在还没有写过单元测试,建议先去学习以下常用的单元测试库[1],只要实践过,才能感受到本文开头提到的那些痛点。

Hypothesis 是一个 Python 库,用于让单元测试编写起来更简单,运行时功能更强大,可以在代码中查找您不会想到的极端情况。它稳定,强大且易于添加到任何现有测试框架中。它的工作原理是让您编写断言每种情况都应该正确的测试,而不仅仅是您偶然想到的那些。

Hypothesis 的基础知识

典型的单元测试需要自己写一些测试用例,然后编写测试函数,通过一段代码运行它,然后根据预期结果检查结果。

Hypothesis 有所不同。它是基于属性进行单元测试。它通过生成与您的规范匹配的任意数据并检查在这种情况下程序是否仍然有效。如果找到了一个失败的用例,它将采用该示例并将其测试用例范围缩减缩减为一定尺寸,然后对其进行简化,直到找到一个仍会导致问题的小得多的示例。然后将其保存,后续单元测试时仍会使用这些用例。

现在就让我们看看怎么用吧。

Hypothesis 快速入门

1、安装

可以通过 pip 安装,也可以通过源代码安装[2],也可以安装一些扩展[3],如下:

1

2

pip install hypothesis

pip install hypothesis[pandas,django]

2、使用

先写一段代码,保存在 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()

最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走! 希望能帮助到你!【100%无套路免费领取】

软件测试小dao
微信公众号
软件测试资料共享(文档、视频、笔记源码)
注:本文转载自blog.csdn.net的yoyo小小汐~的文章"https://blog.csdn.net/weixin_57794111/article/details/144401593"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

127
测试
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top