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

高质量的代码离不开单元测试,而设计单元测试的用例往往又比较耗时,而且难以想到一些极端情况,本文讲述如何使用 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 来自动化单元测试的资料请关注我们其它相关文章!

(0)

相关推荐

  • python3.6编写的单元测试示例

    本文实例讲述了python3.6编写的单元测试.分享给大家供大家参考,具体如下: 使用python3.6编写一个单元测试demo,例如:对学生Student类编写一个简单的单元测试. 1.编写Student类: #!/usr/bin/env python3 # -*- coding: utf-8 -*- class Student(object): def __init__(self,name,score): self.name = name self.score = score def get

  • Python unittest单元测试框架实现参数化

    当我们在使用TestNG时,发现它有一个非常好用的参数化功能.当你的测试用例有固定的参数和断言结果时,它可以相似用例的节省用例的个数. 例子如下: import static org.testng.Assert.assertEquals; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** * Created by fnngj on 2017/3/19. */ public cla

  • 全面介绍python中很常用的单元测试框架unitest

    1.unitest主要功能模块介绍 unitest主要包含TestCase.TestSuite.TestLoader.TextTestRunner.TextTestResult这几个功能模块. TestCase:一个TestCase实例就是一个测试用例,一个测试用例就是一个完整的测试流程,包括测试前环境的搭建,测试代码的执行,以及测试后环境的还原或者销毁.元测试的本质也就在这里,一个测试用例是一个完整的测试单元,可以对某一具体问题进行检查验证. TestSuite:多个测试用例集合在一起就是Te

  • Python unittest单元测试框架及断言方法

    1.单元测试的几个重要概念 (1)Test Case 一个Test Case实例是一个测试用例,完整的测试流程包括测试前准备环境的搭建(setUp).实现测试过程的代码(run).以及 测试后环境的还原(tearDown). (2)Test Suite Test Suite用来组装单个测试用例,可以将多个测试用例集合再一起来执行,通过addTest加载TestCase到TestSuit实例中. (3)Test Runner 执行测试,执行结果.unittest单元测试框架中,通过TextTest

  • Python编写单元测试代码实例

    做一个简单的小实例: 目录结构如下: demo1.py class MyClass(): def __init__(self,x,y): self.x = x self.y = y def add(self): return self.x + self.y def sub(self): return self.x - self.y import unittest from unittset_demo.demo1 import MyClass class MyclassTest(unittest.

  • python单元测试框架pytest的使用示例

    首先祝大家国庆节日快乐,这个假期因为我老婆要考注会,我也跟着天天去图书馆学了几天,学习的感觉还是非常不错的,这是一篇总结. 这篇博客准备讲解一下pytest测试框架,这个框架是当前最流行的python语言最流行的单测框架,不掌握可不行,首先这个框架属于第三方模块,需要通过pip安装即可 pip install pytest 下面我们进入正题 一.介绍pytest的运行规则 1.测试文件的名称必须要以test_*.py的格式,或者*_test.py的格式 2.测试类的名称必须要以Test开头,且这

  • 在Python中进行自动化单元测试的教程

    一.软件测试 大型软件系统的开发是一个很复杂的过程,其中因为人的因素而所产生的错误非常多,因此软件在开发过程必须要有相应的质量保证活动,而软件测试则是保证质量的关键措施.正像软件熵(software entropy)所描述的那样:一个程序从设计很好的状态开始,随着新的功能不断地加入,程序逐渐地失去了原有的结构,最终变成了一团乱麻(其实最初的"很好的状态"得加个问号).测试的目的说起来其实很简单也极具吸引力,那就是写出高质量的软件并解决软件熵这一问题. 可惜的是,软件开发人员很少能在编码

  • Python unittest单元测试openpyxl实现过程解析

    一.初识单元测试 1)定义: 单元:函数或者是类 单元测试:测试类或者函数 python内置的单元测试框架:unittest 2)单元测试的意义 好处:投入小,收益大.能够精准的,更早的发现问题. 3)单元测试与测试关系 python 很难测试 java 的单元. 关键是单元测试一般是开发或者测试开发做的. 测试一般会在集成.系统.验收进行测试 4)unittest的注意事项: 1.模块名需要以 test_ 开头 2.类名:以 Test 开头 3.测试用例的方法名称以 test_ 开头 4.单元

  • Python Unittest自动化单元测试框架详解

    本文实例为大家分享了Python Unittest自动化单元测试框架的具体代码,供大家参考,具体内容如下 1.python 测试框架(本文只涉及 PyUnit) 参考地址 2.环境准备 首先确定已经安装有Python,之后通过安装PyUnit,Python版本比较新的已经集成有PyUnit(PyUnit 提供了一个图形测试界面UnittestGUI.py) 参考:查看地址 3.代码实例 使用的IDE为 PyCharm,DEMO结构如图 1.简单地一个实例 # Test002_Fail.py #

  • Python Django框架单元测试之文件上传测试示例

    本文实例讲述了Python Django框架单元测试之文件上传测试.分享给大家供大家参考,具体如下: Submitting files is a special case. To POST a file, you need only provide the file field name as a key, and a file handle to the file you wish to upload as a value. For example: >>> c = Client()

  • Python基于Hypothesis测试库生成测试数据

    Hypothesis是Python的一个高级测试库.它允许编写测试用例时参数化,然后生成使测试失败的简单易懂的测试数据.可以用更少的工作在代码中发现更多的bug. 安装 pip install hypothesis 如何设计测试数据 通过介绍也许你还不了解它是干嘛的,没关系!我们举个例子. 首先,我有一个需要测试的函数: def add(a, b): """实现加法运算""" return a + b 测试代码是这样的: import unitt

随机推荐