• 售前

  • 售后

热门帖子
入门百科

python 怎样用 Hypothesis 来主动化单位测试

[复制链接]
心灰意冷437 显示全部楼层 发表于 2021-10-26 13:26:11 |阅读模式 打印 上一主题 下一主题
目次


  • Hypothesis 的底子知识
  • Hypothesis 快速入门

    • 1、安装
    • 2、使用
    • 3、其他策略参考

  • 从那里开始

    • 参考资料

高质量的代码离不开单元测试,而计划单元测试的用例通常又比力耗时,而且难以想到一些非常环境,本文讲述如何使用 Hypothesis 来自动化单元测试
刷过力扣算法题的同砚都知道,偶然间以为代码已经很美满了,一提交才发现很多环境没有思量到。然后感叹力扣的单元测试真的牛比。
因此,高质量的代码离不开单元测试,假如现在还没有写过单元测试,发起先去学习以下常用的单元测试库[1],只要实践过,才气感受到本文开头提到的那些痛点。
Hypothesis 是一个 Python 库,用于让单元测试编写起来更简单,运行时功能更强大,可以在代码中查找您不会想到的非常环境。它稳固,强大且易于添加到任何现有测试框架中。它的工作原理是让您编写断言每种环境都应该正确的测试,而不但仅是您偶然想到的那些。

Hypothesis 的底子知识


典范的单元测试须要自己写一些测试用例,然后编写测试函数,通过一段代码运行它,然后根据预期效果查抄效果。
Hypothesis 有所差别。它是基于属性举行单元测试。它通过生成与您的规范匹配的恣意数据并查抄在这种环境下步伐是否仍然有用。假如找到了一个失败的用例,它将接纳该示例并将其测试用例范围缩减缩减为肯定尺寸,然后对其举行简化,直到找到一个仍会导致标题的小得多的示例。然后将其生存,后续单元测试时仍会使用这些用例。
现在就让我们看看怎么用吧。

Hypothesis 快速入门



1、安装

可以通过 pip 安装,也可以通过源代码安装[2],也可以安装一些扩展[3],如下:
  1. pip install hypothesis
  2. pip install hypothesis[pandas,django]
复制代码
2、使用

先写一段代码,生存在 mycode.py 中,功能是对字符串举行特定的编码和解码,内容如下:
  1. def encode(input_string):
  2. count = 1
  3. prev = ""
  4. lst = []
  5. for character in input_string:
  6.   if character != prev:
  7.    if prev:
  8.     entry = (prev, count)
  9.     lst.append(entry)
  10.    count = 1
  11.    prev = character
  12.   else:
  13.    count += 1
  14. entry = (character, count)
  15. lst.append(entry)
  16. return lst
  17. def decode(lst):
  18. q = ""
  19. for character, count in lst:
  20.   q += character * count
  21. return q
复制代码
对这段代码举行单元测试,通常须要写很多测试用例,现在我们使用 hypothesis 来自动为我们测试,编写 test_mycode.py (文件名随意),内容如下:
  1. from hypothesis import given
  2. from mycode import decode,encode
  3. from hypothesis.strategies import text
  4. import unittest
  5. class TestEncoding(unittest.TestCase):
  6. @given(text())
  7. def test_decode_inverts_encode(self, s):
  8.   self.assertEqual(decode(encode(s)), s)
  9. if __name__ == "__main__":
  10. unittest.main()
复制代码
可以看出,这里并没有出现具体的测试用例,而是使用来 text 的策略,相当于 hypothesis 自动穷举来可能的环境,也可以看出它很容易可其他测试框架集成,这里是 unittest。现在来运行一下看看效果:
  1. (py38env) ➜ tmp python test_mycode.py
  2. Falsifying example: test_decode_inverts_encode(
  3. self=<__main__.TestEncoding testMethod=test_decode_inverts_encode>, s='',
  4. )
  5. E
  6. ======================================================================
  7. ERROR: test_decode_inverts_encode (__main__.TestEncoding)
  8. ----------------------------------------------------------------------
  9. Traceback (most recent call last):
  10. File "test_mycode.py", line 9, in test_decode_inverts_encode
  11. def test_decode_inverts_encode(self, s):
  12. File "/Users/aaron/py38env/lib/python3.8/site-packages/hypothesis/core.py", line 1162, in wrapped_test
  13. raise the_error_hypothesis_found
  14. File "test_mycode.py", line 10, in test_decode_inverts_encode
  15. self.assertEqual(decode(encode(s)), s)
  16. File "/Users/aaron/tmp/mycode.py", line 14, in encode
  17. entry = (character, count)
  18. UnboundLocalError: local variable 'character' referenced before assignment
  19. ----------------------------------------------------------------------
  20. Ran 1 test in 0.048s
  21. FAILED (errors=1)
复制代码
这里测试出当字符串为 '' 的时间会抛出 UnboundLocalError 的非常。现在我们来修复这个 bug,然后把所有的测试用例 s 给打印出来,看看它用了哪些测试用例。
encode 函数加入以下代码:
  1. if not input_string:
  2. return []
复制代码
test_mycode.py 文件打印出测试用例:
  1. @given(text())
  2. def test_decode_inverts_encode(self, s):
  3. print(f"{s=}")
  4. self.assertEqual(decode(encode(s)), s)
复制代码
再次实验:
  1. (py38env) ➜ tmp python test_mycode.py
  2. s=''
  3. s='1'
  4. s='0'
  5. s='0'
  6. s='0'
  7. s='Ā'
  8. s='\U000cf5e5'
  9. s='0'
  10. s=''
  11. s='0'
  12. s='0'
  13. s='E'
  14. s=")dù'\x18\U0003deb3¤jd"
  15. s='\U0005bc37\x07\U000537a1ÝÀãiÎ\U000ce9e5\x0b'
  16. s='\U0005bc37\U0005bc37\U000537a1ÝÀãiÎ\U000ce9e5\x0b'
  17. s='\U0005bc37\U000537a1\U000537a1ÝÀãiÎ\U000ce9e5\x0b'
  18. s='À\U000537a1\U000537a1ÝÀãiÎ\U000ce9e5\x0b'
  19. s='\U000965e1\x12\x85&\U000f500aÄÃc'
  20. s='\n\U0004466c\x86Î\x07'
  21. s='Ê\U00063f1e\x01G\x88'
  22. s='ÚV\n'
  23. s='VV\n'
  24. s='\U0008debf湆è'
  25. s='\U0008debf湆è'
  26. s='\U0008debf湆'
  27. s='\U0008debf\U0008debf'
  28. s='\U0008debf\U0008debfó]½àq\x82#\U00015196\U0001c8beg'
  29. s='\U0008debfgó]½àq\x82#\U00015196\U0001c8beg'
  30. s='?'
  31. s='Î'
  32. s='Î\U00085b9e'
  33. s="Î8'?\U00057c38Ù;\x07\U000a5ea8Ò»=\U00091d5b~8뺈"
  34. s='\U000d6497Ý>'
  35. s='\U000e0f01'
  36. s='\U000e0f01Å0y¢KN®'
  37. s='\U000e0f01Å0y¢KN®'
  38. s='\U00050a06'
  39. s='Å\U000b98b3か\U000ba80aá`Ã-Êu\x8c\x90³FÔ"'
  40. s='\x8e\U0004612a\x83ç'
  41. s='\x8e'
  42. s='\x8e\x98\U000fb3e0\U0010d2b3\x10\x82\x94Ð渥'
  43. s='¥W'
  44. s='p\U000e5a2aE·`ì'
  45. s='\U000b80f8\x12\U000c2d54'
  46. s='.\U000703de'
  47. s='6\U00010ffa\U000f7994\x8e'
  48. s='116\U000f7994\x8e'
  49. s='1?6\U000f7994\x8e'
  50. s='4?6\U000f7994\x8e'
  51. s='4\x8e6\U000f7994\x8e'
  52. s='0'
  53. s='\U0006a564´Ð\x93ü\x9eb&i\x1cÑ'
  54. s='\U000ceb6f'
  55. s='\U000ceb6f\xa0\x08'
  56. s='\U000ceb6f\xa0\x08'
  57. s='\U000ceb6fꄃ\x08'
  58. s='\U000ceb6fꄃ匀\U0007cc15\U000b2aaa×**'
  59. s='\U000ceb6fꄃ匀'
  60. s='匀ꄃ匀'
  61. s='J\x14?ö'
  62. s='q)'
  63. s='q)'
  64. s='q\U00060931'
  65. s='q6'
  66. s='\U000e3441'
  67. s='\U000e3441\U00019958¯'
  68. s='\x13'
  69. s='\U000f34dbk'
  70. s='Kp&tÛà'
  71. s='\nö\x93'
  72. s='\n\n\x93'
  73. s='\U00019c8dѳ\U00056cbd\U000e3b2f\U00058d302'
  74. s='\x90=R\x8bß\x03'
  75. s='\x9a'
  76. s='\U000147e7'
  77. s='\U000147e7\x85\U0007a3ef'
  78. s='\U000147e7\U00050a070Â>'
  79. s='\U000a4089\x0eC+RÁ\x02\x97\x9cüÌïSS\U0006cbc5;ÿ~\x16\x019VÇ\U000a32fdQ÷\x15'
  80. s='ÞÚ¾\x19©Z®'
  81. s='ਸ਼æ'
  82. s='\U000cd45a'
  83. s='\U000cd45a\U000e15cbÑ\x08J\ueb3eúß\x07I\x91\x9a\x18\x16Ç\x80\x1a'
  84. s='\x8f}º\x0eq\x0b'
  85. s='\x0e}º\x0eq\x0b'
  86. s="\U000e05a3&¶º[fõ\x8bÜR'ͼt\x97íW\x05\U000caea9\U0008fd74\U000e8f1c¹?dfƾ\x13"
  87. s='\x10\U000e12e2ù\U0006f96erý\U00014baf\x00\x95\U000dbc92É\U00081613µ\U0003b865Z\U0008cc3c'
  88. s='ú\U000b561f\x8fÎ'
  89. s='\tàÖ÷'
  90. s='à\x92©Ì\U000618fa\x92'
  91. s='\U000aaf94\x94\x84\U000cda69\U0005291a\U000a63deþ¿O\x8a>\U000b458bÊ.\U00086f07\x1a'
  92. s='\U0009754e?U_\xa0\x13PQ\x18º\x07\U0006c9c5.Á'
  93. s='\U00102456'
  94. s='³WᵎÕ'
  95. s='\x14\x1c'
  96. s='\x14'
  97. s='\x14\U00105bcd"\x10Ô\x99\U000a5032R\U00056c44V&÷>+\U000aaff2ñ®\U000d7570%ª!\U00032553´8x^«'
  98. s='\x00\U000e2ac4¼ÄUrB'
  99. s='\x00\U000e2ac4¼ÄUrB'
  100. s='\x00\U000e2ac4¼ÄUrB'
  101. s='ª\x1aU\x8aÇ\U000b2fb9\U0005a586'
  102. .
  103. ----------------------------------------------------------------------
  104. Ran 1 test in 0.180s
  105. OK
复制代码
从实验效果可以看出,'' 起首被测试,其次 hypothesis 使用了大量的非常测试用例,减轻了手写的负担,大大提升了服从。
虽然 hypothesis 具有自动影象功能,你仍然可以显式的指定某个测试用例一直被测试,而且这是推荐的做法,比如我想在每次的测试中都测试 '',可以如许写:
  1. from hypothesis import given, example
  2. from hypothesis.strategies import text
  3. @given(text())
  4. @example("")
  5. def test_decode_inverts_encode(s):
  6. assert decode(encode(s)) == s
复制代码
这一点非常有用,提升了测试代码的可读性,可以用来告诉开发职员大概将来的自己,输入的字符串必须要思量 '' 的情况。
别的,实验单元测试,不肯定要使用 unittest.main(),也可以如许,是不是很方便:
  1. if __name__ == "__main__":
  2. test_decode_inverts_encode()
复制代码
3、其他策略参考


从那里开始


以上仅仅是抛砖引玉,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 来自动化单元测试的资料请关注草根技术分享别的干系文章!

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作