lsekfe 发表于 2021-12-21 10:05:14

分钟测试:哪个Python热门单元测试框架适合现在的你?

前言
  对于我们测试来说,做自动化测试需要编写测试用例,编写测试用例就会使用到单元测试模块,常见的python单元测试模块有unittest,pytest,nose等,但是听说过最多的也就属于pytest和unittest了,安静今天就通过这一篇文字简单的介绍下pytest和unittest的区别。

  unittest
  说起单元测试,安静立刻想到的就是unittest框架,为什么呢?因为安静刚开始学习的时候也是学习的unittest框架。unittest属于python的内置框架,被广泛的应用到各个项目中。unittest最早的时候是受到junit的启发,unittest支持多种自动化测试用例的编写,以及支持用例前置条件和后置数据清理功能也可以将多个测试用例放在测试集中生成测试报告。

  pytest
  pytest也是基于python的一个单元测试框架,是基于unittest的一个扩展框架,比unittest更加简洁,方便,pytest最主要可以支持第三方插件内容,可以更加高效的完成日常工作。而且pytest也支持unittest的代码框架内容。

  区别
  安静简单的从几个方面进行来说明pytest和unittest之间的区别内容吧。

  断言方面
  unittest的断言是根据自身携带的断言内容,提供了assertEqual,assertTrue,assertFlase等断言函数。
  pytest的断言是根据Python自带的assert断言内容进行使用的。
import pytest
class Test_01:
    def test_01(self):
      a = 'anjing'
      b = 'anjing'
      assert a == b
    def test_02(self):
      '''
      a是否存在b中
      '''
      a = 'anjing'
      b = 'test_anjing'
      assert a in b
    def test_03(self):
      '''
      结果是否为true
      '''
      a = True
      assert a
    def test_04(self):
      '''
      结果是否为false
      '''
      a = False
      assert a== False
if __name__ == '__main__':
    pytest.main(['-sv'])用例执行编写规则
  unittest:可以使用自定义测试类内容,但必须继承unittest.TestCase的方法,测试用例需要以test的方法进行编写,用例的执行顺序是根据ASCII的顺序进行执行的。目前不能指定特定的用例顺序。unittest中提供了TestCase(测试用例),TestSuite(测试套件),TestLoder(加载用例),TextTestRunner(执行用例)等方法让测试用例更加方便编写,编写测试用例必须导入unittest模块,执行用例必须带有unittest.main()参数。

  pytest:测试文件名必须以test_开头的py文件或者以*_test.py结尾的py文件,测试类名必须以Test开头,测试用例必须以test_开头。Pytest可以进行执行unittest的用例,pytest的执行顺序可以通过第三方插件进行定制顺序,默认是通过从上往下的顺序进行执行。pytest的执行用例不需要导入模块。执行用例可以通过命令行的形式进行执行。

前后置操作
  unittest:可以通过setup()和tearDown()的方法来控制用例的前后置操作,并且每条用例执行前后都会执行前后置操作。通过setupclass()和teardownclass()方法来控制一个class下的所有用例都只执行1次前后置操作。
import unittest

class Test(unittest.TestCase):
    def setUp(self):
      print('unittest前置操作,每次执行用例都会进行执行')
    def tearDown(self):
      print('unittest后置操作,每次执行用例都会进行执行')
    @classmethod
    def setUpClass(cls):
      print('所有用例只执行一次前置操作')
    @classmethod
    def tearDownClass(cls):
      print('所有用例只执行一次后置操作')
    def test_01(self):
      print('用例01')
    def test_02(self):
      print('用例02')
    def test_03(self):
      print('用例03')

if __name__ == '__main__':
    unittest.main()通过执行后发现,setupclass下的操作只执行了用例操作,setup每条用例执行前都会执行。

pytest:模块级别的前后置操作(setup_module,teardown_function)表示模块下的测试用例只执行1次前后置操作。
import pytest
def setup_module():
    print('每次用例执行前,只执行一次测试前置操作')
def teardown_module():
    print('每次用例执行后,只执行一次测试后置操作')
def test01():
    print('用例01')
def test02():
    print('用例02')
def test03():
    print('用例03')
if __name__ == '__main__':
    pytest.main(['-vs'])
函数级别的前后置操作(setup_function,teardown_function)每次执行用例时都需要执行一次前后置操作。

类级别前后置操作(setup_class,teardown_class)表示类级别中测试用例只执行1次前后置操作。

方法级别前后置操作(setup_method,teardown_method)表示是在方法级别中每次都执行的用例的前后置操作。

 从上面的多种setup和teardown可以看出来很方便我们进行编写测试用例,但是pytest不仅仅是这些。pytest中有个固件fixture通过装饰器进行使用,使用范围更加广泛,可以随意命名。可以在fixture中编写前后置操作,通过yield进行区别前置操作和后置操作内容。
import pytest
@pytest.fixture()
def login():
    print('测试用例前置操作')
    yield
    print('测试用例后置操作')
class Test:

    def test01(self, login):
      print('用例01')
    def test02(self,login):
      print('用例02')
    def test03(self):
      print('用例03')

if __name__ == '__main__':
    pytest.main(['-vs'])通过执行发现,我们在用例03中没有加入fixture,所有他没有执行一些用例的前置和后置操作。

测试报告
  unittest:unittest中没有自带的测试报告,需要下载第三方的插件HTMLTestRunner和BeautifulReport来生成详细的测试报告。
  pytest:pytest中也没有自带的测试报告,需要下载第三方插件pytest-html或者allure-pytest进行生成详细的测试报告。
class Test01:
    def test_01(self):
      print('登录用例01')
    def test_02(self):
      print('编写用例02')
    def test_03(self):
      print('删除用例03')通过在命令中输入命令:pytest --html=report.html进行生成报告。

参数化功能
  unittest:unittest本身不支持参数化功能,需要通过第三方库DDt来进行完成参数化功能。
  pytest:pytest的参数化有两种方式,一种通过@pytest.mark.pyparametrize的方法,也可以通过@pytest.fixture()中的方法params来完成参数化内容。

  失败重跑
  unittest:目前unittest暂时不支持用例失败重跑机制
  pytest:可以依赖第三方插件pytest-rerunfailures来进行用例失败重跑
import pytest
import random
class Test01:
    @pytest.mark.flaky(reruns=3, reruns_delay=2)
    def test_01(self):
      a = random.randint(0, 3)
      print('通过随机数来确保用例会有几率失败')
      print('随机数是:{}'.format(a))
      assert a == 2

if __name__ == '__main__':
    pytest.main(['-vs'])执行代码,发现断言失败后会进行重新执行。

跳过用例
  当明知测试用例存在bug或者说该用例进行修改过了,本次执行用例时候,不想执行用例,我们可以进行跳过用例。unittest和pytest都有自带的跳过用例方法。安静通过代码实例进行介绍
  unittest中可以通过装饰器skip的方法进行强制跳过,通过skipif的方法加上条件进行跳过,当条件达到是,进行跳过,条件未达到,不进行跳过用例
  pytest中的跳过和unittest基本相似,跳过用例,一样要输入一些条件。

  实战演示
  上面简单的列举了使用的各个方法,其中有一些区别没有举例给大家说明,安静这里简单的通过请求天气和查询身份证接口的形式,编写一些测试用例,将上述没有介绍的内容,给大家通过实例的方式进行连接起来。

  unittest
  这里安静使用到unittest的参数化是通过ddt的形式,测试数据没有单独存放,直接放到了类外面,安静这里创建了两个接口(查询天气和查询身份证)使用了跳过用例的方法,并对测试接口进行了断言操作,并通过HTMLTestRunner的方法生成了测试报告:
import unittest
import requests
import HTMLTestRunner_cn
import ddt

# 天气接口的参数化数据
data = [{"city": '上海', "key": "331eab8f3481f37868378fcdc76cb7cd", 'result':"查询成功!"},
      {"city": "上海", "key": "331eab8f3481f37868378fcdc76cb7c", 'result':"错误的请求KEY"},
      {"city": "上", "key": "331eab8f3481f37868378fcdc76cb7cd", 'result':"暂不支持该城市"}]

@ddt.ddt
class Test_(unittest.TestCase):
    def tianqi(self,city,key):
      '''天气接口'''
      data = {
            "key":key,
            "city":city
      }
      r = requests.post(url='http://apis.juhe.cn/simpleWeather/query',data=data)
      return r.json()
      
    def shenfenzheng(self,cardno,key):
      '''身份证查询接口'''
      data = {
            "cardno":cardno,
            "key":key
      }
      r = requests.post('http://apis.juhe.cn/idcard/index',data= data)
      return r.json()
      
    @ddt.data(*data)
    def test_01(self,data):
      '''参数化的天气接口'''
      x = self.tianqi(city=data['city'], key=data['key'])
      self.assertEqual(x['reason'], data['result'])
      
    def test_02(self):
      '''正确的身份证号正确的key'''
      cardno = '130428197411155947'   # 身份证信息通过Faker随机创建
      key = "f40a75704fac353952a6534a18f9f437"
      # 请求查询身份证接口
      a = self.shenfenzheng(cardno,key)
      self.assertIn(a['reason'], '成功的返回')
      
    @unittest.skip('强制跳过,不需要条件')
    def test_03(self):
      '''正确的身份证号错误的key(跳过用例)'''
      cardno = '130428197411155947'
      key = "f40a75704fac353952a6534a18f9f43"
      a = self.shenfenzheng(cardno, key)
      self.assertEqual(a['reason'], '错误的请求KEY')
      
    @unittest.skipIf(True, '条件成立时候,进行跳过')
    def test_04(self):
      '''错误的身份证号正确的key(跳过用例)'''
      cardno = '42082120031108929'
      key = "f40a75704fac353952a6534a18f9f437"
      a = self.shenfenzheng(cardno, key)
      self.assertEqual(a['reason'], '请输入正确的15或18位身份证')

if __name__ == '__main__':
    report_path = 'report.html'
    # 打开报告
    fp = open(report_path, 'wb')
    # 报告详情
    runner = HTMLTestRunner_cn.HTMLTestRunner(stream=fp,
                                              title=u'自动化测试报告,测试结果如下:',
                                              description=u'用例执行情况:')
    # 实例化
    testunit = unittest.TestSuite()
    # 加载用例
    testunit.addTests(unittest.TestLoader().loadTestsFromTestCase(Test_))
    # 执行用例
    runner.run(testunit)
    # 关闭报告
    fp.close() 通过执行代码后发现已经可以成功的生成测试报告。

pytest
  pytest这里安静也是通过上述的内容进行了修改,只是这里使用的框架是pytest。但是实现内容是一样的。天气接口进行参数化,身份证接口中2个跳过。在生成测试报告,这里安静生成的测试报告是通过allure来生成的:
import requests
import json
import pytest
# 参数不同值
# 天气接口的参数化数据
data = [({"city": '上海', "key": "331eab8f3481f37868378fcdc76cb7cd", 'result':"查询成功!"}),
      ({"city": "上海", "key": "331eab8f3481f37868378fcdc76cb7c", 'result':"错误的请求KEY"}),
      ({"city": "上", "key": "331eab8f3481f37868378fcdc76cb7cd", 'result':"暂不支持该城市"})]
class TestCase:
    def weather(self, city, key):
      url = 'http://apis.juhe.cn/simpleWeather/query'
      # 查询天气接口参数
      data = {
            'city': city,
            'key': key
      }
      r = requests.post(url, data=data)
      return r.json()
    def shenfenzheng(self,cardno,key):
      '''身份证查询接口'''
      data = {
            "cardno":cardno,
            "key":key
      }
      r = requests.post('http://apis.juhe.cn/idcard/index',data= data)
      return r.json()
    @pytest.mark.parametrize('data',data)
    def test_01(self,data):
      # 调用天气预报接口
      r = TestCase().weather(city= data['city'],key = data['key'])
      assert r['reason'] == data['result']
    def test_02(self):
      '''正确的身份证号正确的key'''
      cardno = '130428197411155947'   # 身份证信息通过Faker随机创建
      key = "f40a75704fac353952a6534a18f9f437"
      # 请求查询身份证接口
      a = self.shenfenzheng(cardno,key)
      assert a['reason'] == '成功的返回'
    @pytest.mark.skip('强制跳过,不需要条件')
    def test_03(self):
      '''正确的身份证号错误的key(跳过用例)'''
      cardno = '130428197411155947'
      key = "f40a75704fac353952a6534a18f9f43"
      a = self.shenfenzheng(cardno, key)
      asserta['reason'] =='错误的请求KEY'
    @pytest.mark.skipif(True, reason='条件成立时候,进行跳过')
    def test_04(self):
      '''错误的身份证号正确的key(跳过用例)'''
      cardno = '42082120031108929'
      key = "f40a75704fac353952a6534a18f9f437"
      a = self.shenfenzheng(cardno, key)
      assert a['reason'] in '请输入正确的15或18位身份证'
if __name__ == '__main__':
    pytest.main(['-s'])这里我们通过在cmd中先执行pytest --alluredir report进行打开报告,然后在通过allure serve report打开测试报告。从报告中就能看到已经全部都执行成功了,且用例跳过了2条。

总结
  安静简单的列举出来了几点并通过项目实例进行对比两种框架的情况。其中我们可以看出来unittest编写用例比pytest复杂,最最最主要的一点是unittest没有较多的第三方插件支持,但是pytest可以支持更多的第三方插件,这样可以更加方便的进行投入到编写测试用例的过程中。当然对于新手来说,安静还是推荐先使用unittest,看看unittest的源码内容,当unittest学会之后在去接触pytest,这样就更加容易上手。其次unittest作为Python中的内置框架,也是值得大家进行学习的。







页: [1]
查看完整版本: 分钟测试:哪个Python热门单元测试框架适合现在的你?