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

 更新时间:2021年03月22日 15:16:42   作者:somenzz  
这篇文章主要介绍了python 如何用 Hypothesis 来自动化单元测试,帮助大家更好的理解和学习使用python,感兴趣的朋友可以了解下

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

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

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

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

Hypothesis 的基础知识

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

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

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

Hypothesis 快速入门

1、安装

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

pip install hypothesis
pip install hypothesis[pandas,django]

2、使用

先写一段代码,保存在 mycode.py 中,功能是对字符串进行特定的编码和解码,内容如下:

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 (文件名随意),内容如下:

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。现在来运行一下看看效果:

(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 函数加入以下代码:

if not input_string:
 return []

test_mycode.py 文件打印出测试用例:

@given(text())
def test_decode_inverts_encode(self, s):
 print(f"{s=}")
 self.assertEqual(decode(encode(s)), s)

再次执行:

(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 具有自动记忆功能,你仍然可以显式的指定某个测试用例一直被测试,而且这是推荐的做法,比如我想在每次的测试中都测试 '',可以这样写:

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(),也可以这样,是不是很方便:

if __name__ == "__main__":
 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 来自动化单元测试的资料请关注脚本之家其它相关文章!

相关文章

  • python库sklearn常用操作

    python库sklearn常用操作

    sklearn是一个无论对于机器学习还是深度学习都必不可少的重要的库,里面包含了关于机器学习的几乎所有需要的功能,本文不会先整体介绍sklearn库,而是先从sklearn库中的一些具体实例入手,感兴趣的朋友一起看看吧
    2021-08-08
  • Python装饰器用法实例总结

    Python装饰器用法实例总结

    这篇文章主要介绍了Python装饰器用法,结合实例形式总结分析了Python装饰器的功能、原理及常见使用方法,需要的朋友可以参考下
    2018-05-05
  • python json.dumps() json.dump()的区别详解

    python json.dumps() json.dump()的区别详解

    这篇文章主要介绍了python json.dumps() json.dump()的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • python os.path模块常用方法实例详解

    python os.path模块常用方法实例详解

    os.path模块主要用于文件的属性获取,在编程中经常用到,以下是该模块的几种常用方法。感兴趣的朋友跟随小编一起看看吧
    2018-09-09
  • 解决cuda和pytorch不兼容的问题

    解决cuda和pytorch不兼容的问题

    这篇文章主要介绍了解决cuda和pytorch不兼容的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • python 根据excel中颜色区分读取的操作

    python 根据excel中颜色区分读取的操作

    这篇文章主要介绍了python 根据excel中颜色区分读取的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-03-03
  • 在python中计算ssim的方法(与Matlab结果一致)

    在python中计算ssim的方法(与Matlab结果一致)

    这篇文章主要介绍了在python中计算ssim的方法(与Matlab结果一致),本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-12-12
  • Python编码类型转换方法详解

    Python编码类型转换方法详解

    这篇文章主要介绍了Python编码类型转换方法,结合实例形式详细分析了Python针对各种常见编码的转码与解码等操作技巧,需要的朋友可以参考下
    2016-07-07
  • 利用Python实现Picgo图床工具

    利用Python实现Picgo图床工具

    这篇文章主要介绍了如何利用Python实现Picgo图床工具,PyPicGo 是一款图床工具,是PicGo是Python版实现,并支持各种插件自定义插件,目前PyPicGo自带了gitee、github、SM.MS和七牛云图传,以及rename、notify和typora等插件,下面来看文章内容介绍,需要的朋友可以参考一下
    2021-11-11
  • Python面向对象程序设计之继承、多态原理与用法详解

    Python面向对象程序设计之继承、多态原理与用法详解

    这篇文章主要介绍了Python面向对象程序设计之继承、多态,结合实例形式分析了Python面向对象程序设计中继承、多态的相关概念、原理、用法及操作注意事项,需要的朋友可以参考下
    2020-03-03

最新评论