暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

pytest系列——参数化的使用

迅捷小莫 2021-09-02
390




Pytest

在pytest中,如何实现参数化呢?我们先来说说为什么需要参数化?

实际场景可能是下面这样:

假如你现在需要写一个登入的测试用例,需要设计几种测试用例如下:

  1. 账号密码正确。预期:登入成功

  2. 账号错误。预期:账号不存在

  3. 账号正确,密码错误。预期:账号或密码错误 

  4. 账号为空 or 密码为空。预期:账号密码为空 

我们通常会把方法写成这样:

    user_account, password = ('admin', '123456')




    def test_login_success():
       """
       登入成功
       :return:
       """
       usr = 'admin'
       psw = '123456'
       # 调用login接口,传入usr和psw,代码省略




    def test_login_usr_not_exist():
       """
       账号不存在
       :return:
       """
       usr = 'admin1'
       psw = '123456'
       # 调用login接口,传入usr和psw,代码省略




    def test_login_psw_error():
       """
       密码错误
       :return:
       """
       usr = 'admin'
       psw = '12345'
       # 调用login接口,传入usr和psw,代码省略




    def test_login_usr_or_psw_is_empty():
       """
       账号或密码为空
       :return:
       """
       usr = ''
       psw = ''
       # 调用login接口,传入usr和psw,代码省略
    复制

    看起来没什么问题,对,确实没什么问题!这是很标准的用例写法,但是我们作为自动化测试工程师,怎么能满足于此。有没有更简便的方式呢?

    我们观察可以看到 ,除了省略那部分一模一样的代码,其他不一样的只是usr和psw两个数据而已,我们只需要每次去改变这两个参数的值就行了 。那么这里就需要用到我们的参数化,所以在这里,参数化的作用是什么?提高编写用例的效率!

    01

    pytest的参数化方式

    在pytest中,我们通常有两种参数化方式:


    1. 使用@pytest.mark.parametrize装饰器

    2. 使用fixture,对没错,又是fixture

    @pytest.mark.parametrize是pytest自带的装饰器之一,我们可以直接使用。而fixture也是很神奇,上篇文章介绍过,fixture是测试固件,可以用来帮助我实现类似setUp,tearDown这些函数,实际上fixture同样可以用来处理参数化。

    02

    使用parametrize实现参数化

    @pytest.mark.parametrize可以直接装饰在任何的测试函数上面,非常的方便,我们改写一下之前的例子,如下:

      import pytest


      test_data = [
         {
             'usr': 'admin',  # 正常登入
             'psw': '123456'
         },
         {
             'usr': 'admin1',  # 账号不存在
             'psw': '123456'
         },
         {
             'usr': 'admin',  # 密码错误
             'psw': '12345'
         },
         {
             'usr': '',  # 账号或密码为空
             'psw': ''
         },
      ]




      @pytest.mark.parametrize('param', test_data)
      def test_login(param): # 这个param需要和上面的'param'一致
         print(param) # 打印




      if __name__ == '__main__':  # 定义主函数
         pytest.main()  # 调用pytest
      复制

      这时候我们尝试执行一下:

      可以看到,现在的测试用例变成了四条,也就是test_data的列表长度,并且这时候的param就是test_data中的每个字典!那我们想要使用字典里的数据那就非常简单了,稍微改写一下代码:

        @pytest.mark.parametrize('param', test_data)
        def test_login(param):
        usr = param.get('usr') # 获取字典中的usr
        psw = param.get('psw') # 获取字典中的usr
        print(f'usr: {usr} , psw: {psw}')
        # 调用login接口,传入usr和psw,代码省略
        复制

        执行结果:

        这样就取到了usr和psw两个字段,非常的简单。但是好像还是没满足我们的需求,少了测试用例的名称,这个pytest也给我们考虑到了,我们可以使用@pytest.mark.parametrize中有个ids字段,这个字段可以设置测试用例的标题,所以我们改造下代码:

          test_data = [
          {
          'case': '登入成功',
          'usr': 'admin', # 正常登入
          'psw': '123456'
          },
          {
          'case': '账号不存在',
          'usr': 'admin1', # 账号不存在
          'psw': '123456'
          },
          {
          'case': '密码错误',
          'usr': 'admin', # 密码错误
          'psw': '12345'
          },
          {
          'case': '账号或密码为空',
          'usr': '', # 账号或密码为空
          'psw': ''
          },
          ]




          @pytest.mark.parametrize('param', test_data, ids=[data.get('case') for data in test_data]) # ids需要传入一个列表,我们利用列表推导式
          def test_login(param):
          usr = param.get('usr')
          psw = param.get('psw')
          print(f'usr: {usr} , psw: {psw}')
          # 调用login接口,传入usr和psw,代码省略




          if __name__ == '__main__': # 定义主函数
          pytest.main() # 调用pytest
          复制

          呆莫,执行的结果不如人意:

          可以看到这时候出现了一些乱码,所以我们需要对乱码进行处理:

          1. 新建一个conftest.py文件

          2. 在里面加入下面一段代码:

            def pytest_collection_modifyitems(items):
            """
            修改用例名称中文乱码
            :param items:
            :return:
            """
            for item in items:
            item.name = item.name.encode('utf-8').decode('unicode_escape')
            item._nodeid = item.nodeid.encode('utf-8').decode('unicode_escape')
            复制

            这里利用的实际上是pytest中的Hook机制,这块内容暂且先不介绍,后续会给大家介绍。总的来说,这就是个处理乱码的Hook函数。

            加了之后,我们再去执行一下,发现这个时候已经没有问题了:

            03

            使用fixture实现参数化

            那么下面我们再看一下fixture是如何实现参数化的?它为什么能实现参数化?

            fixture提供了这么一个机制,fixture装饰的函数拥有一个内置的对象request,同时fixture中还有一个params参数是用来传递参数化数据的,直接上代码:

              import pytest  # 导入pytest


              test_data = [
                 {
                     'case': '登入成功',
                     'usr': 'admin',  # 正常登入
                     'psw': '123456'
                 },
                 {
                     'case': '账号不存在',
                     'usr': 'admin1',  # 账号不存在
                     'psw': '123456'
                 },
                 {
                     'case': '密码错误',
                     'usr': 'admin',  # 密码错误
                     'psw': '12345'
                 },
                 {
                     'case': '账号或密码为空',
                     'usr': '',  # 账号或密码为空
                     'psw': ''
                 },
              ]




              @pytest.fixture(params=test_data)  # 给params传入参数化数据
              def param_data(request):
                 return request.param  # 返回request对象中的param,这里存放的就是参数化数据




              def test_login(param_data): # 测试函数传入fixture
                 usr = param_data.get('usr')
                 psw = param_data.get('psw')
                 print(f'usr: {usr} , psw: {psw}')
                 # 调用login接口,传入usr和psw,代码省略




              if __name__ == '__main__':  # 定义主函数
                 pytest.main()  # 调用pytest
              复制

              在上面的代码中,我们定义了一个fixture,把参数化的数据传入了params,同时给fixture设置了返回request.param。在测试函数中,我们把定义好的fixture传入即可。通过fixture我们就能拿到每次迭代的参数化数据了!

              那么?fixture支不支持传入用例名称呢?当然是支持的!fixture也给我们提供了ids的参数,如下:

                @pytest.fixture(params=test_data, ids=[data.get('case') for data in test_data])  # 给params传入参数化数据,ids传入case名称列表
                def param_data(request):
                   return request.param  # 返回request对象中的param,这里存放的就是参数化数据
                复制

                执行结果如下:

                可以看到左侧的用例标题和右侧的输出都很完美,没有问题!不过要记得conftest.py中的Hook函数同样也是需要的呢。

                那么本期内容到此结束,有什么问题可以及时联系小提莫,会及时进行解答,谢谢关注!


                关注我们


                扫二维码


                文章转载自迅捷小莫,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                评论