Python 记一次复杂化的测试方法抽象成简单化的一键测试

Pengg · 2019年02月26日 · 最后由 lyyyyyyy 回复于 2019年06月05日 · 2123 次阅读

前言

  对于后端测试来说,负责的需求大多数都是复杂度较高,构造的测试场景麻烦,需要校验的数据较多,有的时候还得根据不同的规则来判断输出结果,除了负责测试需求之外,有的时候也需要给前端或者其他业务线的同事配置不同的场景供他们使用。有没有想过,我们是不是可以给自己或者他们提供一种能力,让他们自己一键操作,让我们一键操作,然后就可以关机回家(开玩笑的,反正是不可能下班的)。。。

背景

被调到支付项目组三个月了,发现了一些问题,经常把我搞得焦头烂耳

  1. 自身所负责的业务
    1. 代收测试除了观察接口返回的值之外,还要看 8 张表的数据,除了每条数据的字段,既然还有各种规则穿插在里面(反正我看了两个月每次都头大)
    2. 新创建一个路由规则,要配置 12 张表关系,即使测了大半年的测试每次配置新的路由规则都要定位半天(这是我见过最恐怖的,没有之一)
    3. 使用原有的路由配置规则,每次支付总有失败,然后又得一个个开 xshell,一个个开项目日志看报错查漏了啥没配,甚至配错
    4. 记账才是最头痛的,因为记账记得太细了,从最上层记到最下层总共 21 条记录(如果我不是太熟我都不敢测这个)
      。。。。。。
  2. 对外支持的
    1. 业务方 A 新的需求,需要使用我们路由规则,之后就被抽调支撑
    2. 业务方 B 新的需求,需要使用我们的支付,但是他想看他的每一步支付订单失败原因,之后又被他经常骚扰
      。。。。。。

如何引进一种能力来简单化呢?

  1. 对业务要了如指掌
      这里指的是了如指掌,并不熟悉。你是要对各种业务场景进行简化操作和验证输出结果,没有业务的支撑你是无法做到的。除非你是公用业务组那就另当别论。
  2. 不要做无效的能力输出
      回过头来,慢慢回忆和思考这段时间遇到的问题,其实是有很多我们都可以把它转换成一种能力。如我们业务的代付测试,需要校验输出结果,但是已经有成熟的管理后台可以看到结果,我们就不应再提供一种能力来帮助我们测试。应关注繁琐、复杂,同时又没有很好的方式来提高测试效率。
  3. 不要做重复的测试
      虽然自动化本身是可以覆盖这些,但是自动化更多的是用来覆盖基本场景。针对新开发的需求就不适用,如我们这边的代扣,虽然新增需求通过各种各样的支付方式,但是最后入库和需要校验的参数没有变化,那么就能在这块提供一种能力,无论以后你怎么变化,只要这块不变都能复用。配置业务也是同样的道理,繁琐、复杂的配置规则也能转换成一种能力,供别人和自己使用。
  4. 按照项目的性质大小不同,引入测试工具和测试脚本
      项目有很多有活动、支付、资产、运营等等,但是并不是所有能做,如果项目迭代速度太快就没必要了,反正弄完了估计最多两周就不使用了。挑选和其他业务线有交集较多、自己本身业务线使用较多来动手。敲定好了需要覆盖的场景,就是技术的选型了,有很多 python 的就有 flask、django,前端 vue、element、bootstrap、java 的 springboot 等等。

如何技术选型呢?

建议选择复用性高、开发周期短、框架成熟、本身封装了很多上层可以使用的类,我 java 薄弱,对 python 相对比较熟悉,前端靠语感,选择前段后分离

  1. 后端
    1. django(谁用过都知道好用)
      采用了 MVC 的框架模式,即模型 M,视图 V 和控制器 C。理解了框架的一个请求从客户端发出到服务端返回的过程基本就入门了,再来理解 view、url、template、models、app 恭喜你可以撸码了。
    2. restframwork(符合 restful 的核心思想)
      对 django 封装,符合 restful 思想,根据 http 的 method 方式 get(查询)、post(新建)、put(更新)、delete(删除),满足对数据库增删改查。又提供了序列化 serializers、数据过滤 filter。
  2. 前端
    1. vue(我也没有前端经验,看 topolist 入了个门)
      属性单项绑定,数据的双向绑定,组件化。各种 vue 指令已经帮你做好了 for、if 操作
    2. element-ui(管理后台模板不二之选)
      基于 vue 开发的适用于管理后台模板
    3. axios
      配套 vue,用于 api 请求

功能思路

根据背景中提到的问题,我对自己需要完成的功能分成三块。

  • 快捷测试
    集成工作中用的比较多的 httprunner 框架,用的比较多的接口放上去。
  • 数据评测(一期先做他)
    根据不同场景,对输出的数据进行校验。主要有记账、代收、代付
  • 集成工具
    自己小组用的比较多的业务配置和对外经常需要配置的业务,简化成一键配置

设计思路

最近需求较多,目前只做了数据评测
按照 django 和 restframwork 框架思路基础上增加了获取外部数据和输出结果规则。前端发起 get 请求。后端根据参数到外部数据库拿去对应信息,并对输出信息进行规则校验,入库自身 models 再返回给前端。

后端
  代码不多,django 和 restframwork 已经高度封装底层,功能很好实现,后续规则太多考虑加入 celery。
代码结构

views(继承 mixins.ListModelMixin, viewsets.GenericViewSet)

from rest_framework import mixins, viewsets, filters, status
from django_filters import rest_framework
from rest_framework.response import Response
from .serializers import PaymentPaySerializer
from .filters import PaymentPayFilter
from .models import Payment_pay
from .manipulation import PaymentPayManipulation

class PaymentPayViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    代扣详情
    """
    serializer_class = PaymentPaySerializer
    filter_backends = (rest_framework.DjangoFilterBackend, filters.OrderingFilter,)
    filter_class = PaymentPayFilter
    queryset = Payment_pay.objects.all()
    ordering_fields = ("id",)

    def get_queryset(self):
        return Payment_pay.objects.all()

    def list(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=self.request.query_params)
        serializer.is_valid(raise_exception=True)

        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)

        # 从代收中心和交易中心获取数据入库
        self.insert_pay_data(self.request)
        # 重组和序列化(models我定义是text字段需要转dict给与前端,自定义modelToJson)
        trade_order_map = serializer.data[0]["trade_order_map"]
        serializer.data[0]["trade_order_map"] = self.modelToJson(trade_order_map)

        return Response(serializer.data)
    def insert_pay_data(self, request):
        """
        插入外部支付数据
        :param request:
        :return:
        """
        order_no = request.query_params["order_no"]
        pay_type = request.query_params["pay_type"]
        env = request.query_params["env"]

        #  从外部获取数据(这块代码就不贴了,用的是pymysql库,怎么方便怎么来)
        manipulation = PaymentPayManipulation()
        pay_records = manipulation.need_pay_data(order_no=order_no, pay_type=pay_type, env=env)

        obj = Payment_pay.objects.filter(order_no=order_no,env=env,pay_type=pay_type,trade_order_map=pay_records["trade_order_map"])

        if not obj:
            Payment_pay.objects.create(order_no=order_no)
        else:
            Payment_pay.objects.filter(order_no=order_no).update(env=env,pay_type=pay_type,trade_order_map=pay_records["trade_order_map"])

serializers(序列化,继承 serializers.ModelSerializer)

from rest_framework import serializers
from .models import Payment_pay


class PaymentPaySerializer(serializers.ModelSerializer):
    """
    代扣详情序列化
    """
    class Meta:
        model = Payment_pay
        fields = "__all__"

    def validate(self, attrs):
        return attrs

filter(过滤,继承 django_filters.rest_framework.FilterSet)

import django_filters
from .models import Payment_pay


class PaymentPayFilter(django_filters.rest_framework.FilterSet):
    """
    过滤代扣详情
    """

    order_no = django_filters.CharFilter(field_name='order_no', lookup_expr='exact', label="订单号")
    env = django_filters.CharFilter(field_name='env', lookup_expr='exact', label="环境")
    pay_type = django_filters.CharFilter(field_name='pay_type', lookup_expr='exact', label="支付方式")

    class Meta:
        model = Payment_pay
        fields = ['order_no', 'env', 'pay_type']

regulation(框架没有,自己定义用来放规则,只要数据不变,规则可改可加)

class PaymentPayRegulation(object):
    """
    代扣详情数据规则
    """
    @staticmethod
    def trade_order_map_rule(data_list):
        re_list = []

        for data in data_list:
            data["trade_status_notes"] = "0-初始化,1-等待,2-成功,3-失败,4-过期,5-部分成功"
            data["is_trade_status"] = data["trade_status"]
            re_list.append(data)
        return re_list

前端
代码结构

前端页面显示 (引用组件)

<div v-if="trade_order_isshow">
    <br/>
    <el-tag >交易中心trade_order</el-tag>
    <tradeOrder-table :datalist="trade_order_map_data"></tradeOrder-table>
</div>

前端表单显示(组件化 el-table-column)

<template>
    <el-row :gutter="20">
    <el-table
            :row-style="rowStyle"
            :data="datalist"
            style="width: 100%; height: 100%;">
        <el-table-column type="expand">
            <template slot-scope="props">
                <el-form label-position="left" inline class="demo-table-expand">
                    <el-form-item label="pay_order_no">
                        <span style="margin-left:100px;">{{ props.row.pay_order_no }}</span>
                    </el-form-item>
                    <el-form-item label="order_status">
                        <el-tooltip placement="top">
                            <div slot="content">{{ props.row.order_status_notes }}</div>
                    <span style="margin-left:100px;" v-if="props.row.is_order_status === 2">{{ props.row.order_status }}</span>
                    <span  v-else style="color: red;margin-left:100px;">{{ props.row.order_status }}</span>
                        </el-tooltip>
                    </el-form-item>
                </el-form>
            </template>
        </el-table-column>
        <el-table-column
                label="pay_order_no"
                prop="pay_order_no">
        </el-table-column>
        <el-table-column
                label="order_status"
                prop="order_status">
        </el-table-column>
    </el-table>
    </el-row>
</template>
<script>
    export default {
        props: ['datalist'],
        methods:{
            rowStyle({ row, rowIndex }) {
                for (let i=0;i<this.datalist.length;i++)
                {
                    if (this.datalist[i].is_order_status !== 2)
                    {
                        if(rowIndex === i){
                            return 'background-color: pink'
                        }
                    }
                }
            },

        }
    }
</script>

效果
评测成功

可查看详情

评测失败

总结

  复杂的配置、繁琐的场景、恶心的结果校验都可以抽象成简单对象,为的就是提高测试效率,达到一键测试,早日下班。

共收到 1 条回复 时间 点赞
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册