自动化测试中对数据恢复的思考与实际业务改造实践

news2025/2/27 5:27:24

在接口自动化测试过程中,构造测试数据是必不可少的一个环节,但如何恢复测试数据也同样值得关注。业内常见的做法有:

1、不恢复:如果是没什么影响的数据,不恢复也无所谓,缺点就是会造成大量数据冗余;另外如果每次构造的都是重复数据且接口存在重复性校验,那么势必会造成接口报错、数据构造失败,从而影响到测试流程;

2、备份/恢复整个数据库:这种做法最简单粗暴,前提是要先备份数据库,然后在执行完测试后恢复数据库;这种对环境有要求,比如最好是一套独立的、可供自动化测试随便折腾的环境,如果在测试的环境还有其他人在用,那么这种做法显然不可取;

3、手动删除数据:可以是页面上手动删除,或者是调用单独的删除接口删除,也可以是手动删除各个数据表中的数据;

4、调用删除接口,形成闭环:这也是目前最常见的做法,即调用新增接口–>查询接口–>修改接口–>删除接口,从而形成一个完整的闭环,既达到了接口业务流程验证的目的,也避免了手动删除测试数据的麻烦;

以上几种方法中,最后一种是最便捷、也是应用最为广泛的。

在这里插入图片描述

但现实中,可能部分接口的业务流程并不存在完全闭环的情况,比如我们某个项目有个新增企业的业务,如果按照方式4,那么调用顺序就是新增企业–>修改企业信息–>查询企业信息并断言修改字段是否生效–>删除企业。但实际项目中只有增、查、改接口,并没有删除接口(设计如此)。尤其是新增接口,先会调用一个查询接口,获取第三方数据库视图中的企业列表,拿到添加企业信息的相关字段,再调用新增接口添加到我们系统中来,新增时会校验该企业信息是否已存在,不存在则新增,存在则返回错误码。而在没有提供删除接口的情况下,自动化测试过程中就要确保:

  • 要么每次新增的企业数据都不一致(这样就可以不用删除数据,前提是第三方视图中的数据量够大,可以保证一直新增下去);
  • 要么每次新增完数据再执行SQL语句一一删除相关数据表中新增企业时产生的数据(这样就相当于曲线救国式地完成了业务闭环,因为即使是提供了删除接口,其背后也是执行相关SQL进行删除各个数据表数据的操作)。注意这里不能是手动删除数据,无论是手动从页面删除还是手动从数据库删除。因为如果运行一次就删除一次,那运行一百次就要手动删除一百次,显然不切实际,这样也与我们自动化的理念背道而驰。

之前我们组小伙伴所写的自动化测试用例中,使用的是上述第一种方式,即每次新增不一样的企业数据,新增后不删除(原因是开发没有提供删除接口,SQL语句涉及的表较多,且表与表之间存在诸多关联,刚好视图中的数据够多,可以一直添加下去,只要保证每次添加的数据不一致就可以了)。这样的实现方式也不是不可以,只是会带来诸多弊端,而今天文章所表述的内容,就是对该实现方式进行优缺点分析,并基于第二种执行SQL删除数据的实现方式进行改造。

一、分析利弊

1.原实现方式

上述背景中已经提到视图中的数据够多,可以一直添加下去,只需保证每次添加的数据不一致即可。添加企业数据前会先调用查询接口,读取第三方企业数据列表,那么就需要设计一种方式使每次读取到的企业数据不一致。原实现方式如下:

① 定义读取和写入ini配置文件的方法

配置文件内容如下:

[company_data]
x = 1  # page
y = 1000  # page_size
next_y = 242  # index

[product_data]
x = 1
y = 1000
next_y = 146

读写配置文件方法如下:

import configparser, os


def get_next_data(ini_name):
    """
    ini_name:配置文件中,配置的名字,写在[]里面的那串
    方法返回列表,按顺序分别是  x,y,next_y
    对应的意思是  当前要请求的页码,当前请求的条数,当前从第几条获取
    """
    # 取文件路径
    path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    # 合成配置文件的路径
    ini_path = path + '/config/page_config.ini'
    cf = configparser.ConfigParser()
    cf.read(ini_path)
    # 取上一次请求的页码
    x = int(cf.get(ini_name, 'x'))
    y = int(cf.get(ini_name, 'y'))
    next_y = int(cf.get(ini_name, 'next_y'))

    if next_y + 1 < y:
        # 如果+1还小于当前页码数据,那么下次还在当前页数据
        cf.set(ini_name, 'next_y', str(next_y + 1))
    else:
        # 如果+1等于当前页码数据,那么下次就翻页,x也要+1
        cf.set(ini_name, 'x', str(x + 1))
        cf.set(ini_name, 'next_y', str(0))
    cf.write(open(ini_path, 'w'))
    return [x, y, next_y]

在上述方法中,会先读取ini文件中的x、y和next_y,也就是page、page_size、index,如x=1,y=1000,next_y=242 作为入参传给查询接口,x表示请求第1页,y表示每页1000条,next_y则作为索引值、提取这1000条数据中的第242条数据

② 测试用例中调用该方法,读写配置文件

import api_test.config.page_config as page_config

@pytest.mark.rs_smoke
@allure.story("企业管理")
def test_company_manager(self, rs_resource, rs_admin_login, rs_get_admin_user_info, use_db):
    """测试企业增改查接口"""
    user_id = rs_admin_login
    cpy_id = rs_get_admin_user_info
    # 调用方法获取上次读取的位置
    config_data = page_config.get_next_data("company_data")
    x = config_data[0]
    y = config_data[1]
    next_y = config_data[2]
    get_company_list = rs_resource.get_company_list(cpy_id, user_id, x, y)
    company_list = get_company_list["d"]
    company_name = company_list[next_y]["a"]
    company_simple = company_list[next_y]["a"]
    company_num = company_list[next_y]["d"]
    company_manager = self.fake.name()
    company_phone = self.fake.phone_number()
    company_pwd = 123456
    company_type = 3
    sort_id = str(random.randint(1, 100))

    try:
        with allure.step("调用添加企业接口"):
            add_company = rs_resource.add_company(cpy_id, user_id, company_name, company_manager, company_phone,
                                                  company_pwd, company_simple, company_num, company_type)
            # 当错误码返回254,说明该企业信息已存在
            # 此时再调用get_next_data方法,next_y +1
            while add_company["a"] == 254:
                config_data = page_config.get_next_data("company_data")
                x = config_data[0]
                y = config_data[1]
                next_y = config_data[2]
                get_company_list = rs_resource.get_company_list(cpy_id, user_id, x, y)
                company_list = get_company_list["d"]
                company_name = company_list[next_y]["a"]
                company_simple = company_list[next_y]["a"]
                company_num = company_list[next_y]["d"]
                add_company = rs_resource.add_company(cpy_id, user_id, company_name, company_manager, company_phone,
                                                      company_pwd, company_simple, company_num, company_type)
        assert add_company["a"] == 200
        self.company_user_id = add_company["d"]
        select_db = use_db.execute_sql(
            f"SELECT * FROM t_r_company_base WHERE user_id = {self.company_user_id}")  # 查询数据库是否存在新增的数据
        assert company_name in str(select_db)

从上面的测试用例可以看出:

1、测试用例会先调用读取ini配置文件的方法get_next_data,读取到的配置为x=1、y=1000和next_y=242

2、将配置作为参数传递给get_company_list接口,获取企业列表并提取第242条数据

3、将第242条数据相关字段值传递给add_company新增企业接口,若code为200表示新增成功,若返回的code码是254,则表明企业信息已存在

4、当企业信息已存在时,再次调用读取配置文件方法get_next_data,更改ini配置文件、使next_y+1,提取第243条数据,并继续新增企业,断言返回值,以此循环,直到拿到的是不重复的数据、新增企业成功

5、最后查询数据库,断言返回值中的企业ID与数据库查到的是否一致

2.优缺点分析

在真正回归测试过程中,上述方案是可以正常运行的,但也面临诸多问题,下面深入分析该设计的优缺点:

优点

方便,运行完了,产生的数据可以不用处理,整个过程无需人工干预;

缺点

造成大量数据冗余:因为运行后的数据放任不管,如果运行1000次,那么数据表中就会产生1000条企业数据和相关关联数据,如果次数更多,冗余数据必定会更多,也会给前端页面的使用造成一定的压力;

运行次数有上限:因为是通过配置文件,不断读取第三方视图中的企业数据,然后添加这些企业数据到我们的系统中,尽管目前三方企业数据量较多,大概有一万多条,但总有用完的一天,如果运行一万次,最后一页的最后一条也被添加过了,就会达到数据量上限,那么再次运行必定会造成数据重复、添加失败;

调试不方便、执行效率低:我一直比较倾向于配置写在py文件中,而不是yaml或ini文件中,这样就可以不用再定义一个单独的方法来读取配置,从而减小层层调用而导致的出错概率。不过这个不是重点,重点是会造成执行效率慢的问题,因为我们组内代码是同步到gitlab的,配置文件也包含在内。但如果其中A人员执行了50次,next_y会从242自增到292并写入配置文件,而B人员此时没有更新代码,配置文件的next_y依旧为242,那么他执行时前50次都会重复,因为那50条数据已经被用过了,直到next_y自增到293条数据才能使用,这50条while循环读写产生的时间就是浪费的时间,如果中间相差的次数更多500甚至5000次,那么执行效率就会更加低下;

配置文件不统一,git管理不便:试想一下,每执行一次测试用例,配置文件(nexy_y)就会发生改变一次,git文件状态也就变化一次,也就意味着每执行一次就要提交一次代码!或许你会说不提交就是了,那如果A、B两个测试人员的代码长期不统一,就会陷入上一个问题中由配置文件长时间不同步而造成的执行效率低下的因果循环中去;

说了这么多,总结下来就是:配置文件如果提交,就意味着每次都要提交,然后每个人在执行前git pull一次、执行完了git push一次;不提交的话就意味着每个人的配置文件不一样,其中一个人运行的次数越多,其他人运行的效率就越低。好吧,确实挺痛苦。说到底还是设计模式的问题,配置文件在git管理下不能轻易产生变动,运行结果的好坏更不能严重依赖配置文件!

二、改造过程

既然面临那么多问题,那就只有改造了,俗话说“不破不立”!写代码,不仅要保证代码能运行起来,还要从各个方面确保代码的健壮性。设计产品和设计测试用例也是一样。以下是整体业务分析和测试用例改造过程:

1.日志分析

早在开始之初就听说添加企业这个接口涉及的表查询、插入特别多,会产生诸多的业务关联数据,处理起来也会比较麻烦,今日一试,果然如此。

开发没有提供删除接口,但有新增接口,如果我能找到新增企业时插入了哪些数据,再反向将这些数据一一删除,不也就相当于实现了删除接口的功能了吗?说干就干,先分析接口日志(过程确实比较长,核心思想就是:根据后台日志一点一滴梳理数据的流向,插入了哪张表、哪些数据,并找出数据表中的唯一能代表该条数据的字段值,如ID、手机号等,以便于后面设计删除该条数据的SQL语句):

接口请求及参数

我一遍手动新增一个企业,一遍观察后台日志。在新增企业接口请求及返回的打印日志如下:

在这里插入图片描述

查询手机号

在这里插入图片描述

在插入账户表前,会先调用一个c.f.r.m.AccountMapper.queryUserPhone的方法,查询数据库中是否存在相同的手机号,不存在则返回0,只有返回数据为0 ,才会执行后续的插入数据操作

调用注册接口

如果手机号不重复,则会调用注册接口,成功后返回数据:respContent: 473036,即用户的user_id

在这里插入图片描述

后面就是调用注册接口后一系列的关联表查询和写入数据了

t_r_user_info 表写数据

在这里插入图片描述

insert into t_r_user_info ( user_id, user_name, phone, `status`, product_id, create_time ) values ( 473036, 浙江华甸, 13912300000, 1, 2022, 1667443614864 ) 

查看数据表,数据用户信息数据已经写入,由于手机号是唯一的,所以可以根据手机号删除本表对应数据

在这里插入图片描述

t_r_account 账户表写数据

在这里插入图片描述

select user_id, account_name, pass_word, `status`, product_id, create_time, update_time, ext1, ext2, ext3 from t_r_account where account_name = 13912300000 and product_id = 2022 and status = 1 limit 1 

查看数据表,账户数据已经写入,统一也可以根据手机号删除本表对应数据

在这里插入图片描述

t_r_post 岗位表查询数据

在这里插入图片描述

select id, company_id, post_code, post_name, remark, `status`, creator, create_time, ext1, ext2, ext3,classify from t_r_post where post_code = 16 and company_id = 0 order by status DESC limit 1

查询post_code为16,company_id为0的这条数据

在这里插入图片描述

因为是查询操作,所以无需还原数据

t_r_user_post 人员岗位表写数据

在这里插入图片描述

insert into t_r_user_post ( user_id, post_id, post_code, post_name, creator, create_time ) values ( 473036, 4, 16, 客商调度, 429381, 1667443614864 )

插入后的数据如下,可以根据id和user_id来删除本条数据

在这里插入图片描述

t_r_post 岗位表查询数据

在这里插入图片描述

select id, company_id, post_code, post_name, remark, `status`, creator, create_time, ext1, ext2, ext3,classify from t_r_post where post_code = 17 and company_id = 0 order by status DESC limit 1

查询到的数据如下:

在这里插入图片描述

因为是查询操作,所以无需还原数据

t_r_user_post 人员岗位表写数据

在这里插入图片描述

insert into t_r_user_post ( user_id, post_id, post_code, post_name, creator, create_time ) values ( 473036, 7, 17, 客商管理员, 429381, 1667443614864 ) 

插入数据如下:

在这里插入图片描述

其实稍微观察一下就可以发现,t_r_user_post表写了两次数据,也就是插入了两条user_id为473036的数据(因为新增的用户会默认同时存在两个角色,因此本表会插入两条数据,一个岗位ID为16-客商调度,一个岗位ID为17-客商管理员)。所以可以根据user_id删除对应数据

在这里插入图片描述

t_r_user_company 人员客商关系表写入数据

在这里插入图片描述

insert into t_r_user_company(user_id, cpy_id, type, create_time, creator_id, updater_id, update_time, ext1, ext2, ext3,company_id) values (473036, 10306, 2, 1667443614864, 429381, null, null, null, null, null,10306) 

插入数据如下,可以根据user_id删除对应数据
在这里插入图片描述

t_r_company_extend 企业扩展表写数据

在这里插入图片描述

insert into t_r_company_extend (company_id, ext_name, ext_code, ext_value, create_time) values (10306, 简称,cpySimpleName, 浙江华甸防雷科技, 1667443614864) , (10306, 企业编码,cpyCode, xchdfl, 1667443614864) 

根据日志和SQL语句可以得知,插入了两条数据,插入的数据如下图所示。后续可以根据cpy_id删除本表对应数据
在这里插入图片描述

t_r_location 坐标表写入数据
在这里插入图片描述

insert into t_r_location ( company_id, `name`, addr, addr_detail, addr_point, `status`, contact_company, contact_name, contact_phone, user_id, create_time ) values ( 10306, 浙江华甸防雷科技, 山西省-长治市-襄垣县, 山西省长治市襄垣县古韩镇山西机电职业技术学院(东湖校区), 113.065637,36.542198, 1, 浙江华甸防雷科技股份有限公司(String), 浙江华甸, 13912300000, 473036, 1667443614864 ) 

插入数据如下,后续可以根据company_id删除记录表中的数据

在这里插入图片描述

t_r_location与t_r_company_base 坐标表与企业基础表连表查询数据

select l.id, l.company_id, l.`name`, l.addr, l.addr_detail, l.addr_point, l.`status`, l.contact_company, l.contact_name, l.contact_phone, l.user_id, l.create_time from t_r_location l left join t_r_company_base b on l.company_id = b.id where b.ext1 = 1 and b.status = 1

查询到的数据如下:

在这里插入图片描述

因为是查询操作,所以无需还原数据

t_r_transport_line 运输线路表中插入数据

在这里插入图片描述

insert into t_r_transport_line ( company_id, `name`, load_location_id, unload_location_id, create_time, creator_user_id ) values ( 10306, 旭阳煤化工的简称2-浙江华甸防雷科技, 10000, 10306, 1667443614864, 429381 )

插入数据如下,因为线路是双向的,所以有两条数据。后续可以根据company_id删除数据

在这里插入图片描述

t_r_transport_line_point 线路途经点信息表写入数据

在这里插入图片描述

insert into t_r_transport_line_point(line_id, location, sort, type, status, remark, create_time, creator_id, updater_id, update_time, ext1, ext2, ext3) values (10610, 113.065637,36.542198, 1, 1, 1, 山西省长治市襄垣县古韩镇山西机电职业技术学院(东湖校区), 1667443614864, 429381, null, null, null, null, null) , (10610, 118.92252,40.410167, 0, 1, 1, 河北省秦皇岛市青龙满族自治县青龙镇小楸子沟门, 1667443614864, 429381, null, null, null, null, null) , (10611, 113.065637,36.542198, 0, 1, 1, 山西省长治市襄垣县古韩镇山西机电职业技术学院(东湖校区), 1667443614864, 429381, null, null, null, null, null) , (10611, 118.92252,40.410167, 1, 1, 1, 河北省秦皇岛市青龙满族自治县青龙镇小楸子沟门, 1667443614864, 429381, null, null, null, null, null) 

根据日志和SQL语句可以得知,插入了4条数据,插入的数据如下图所示

在这里插入图片描述

这里数据删除比较麻烦,这里要先从t_r_transport_line表中根据company_id查到id,该id也就是t_r_transport_line_point表中的line_id,再根据line_id进行删除。

执行指定实现类

在这里插入图片描述

t_r_orgination 组织表写入数据

在这里插入图片描述

一共执行5条insert操作

insert into t_r_orgination ( code, `name`, parent_id, `type`, company_id, product_id, create_time ) values ( 10306, 浙江华甸防雷科技股份有限公司, 0, 1, 10306, 2022, 1667443614864 )

insert into t_r_orgination ( `name`, parent_id, `type`, company_id, product_id, create_time ) values ( 运输中心, 13442, 2, 10306, 2022, 1667443614898 ) 

insert into t_r_orgination ( `name`, parent_id, `type`, company_id, product_id, create_time ) values ( 司机, 13443, 2, 10306, 2022, 1667443614898 ) 

insert into t_r_orgination ( `name`, parent_id, `type`, company_id, product_id, create_time ) values ( 押运员, 13443, 2, 10306, 2022, 1667443614898 ) 

insert into t_r_orgination ( code, `name`, parent_id, `type`, company_id, product_id, create_time ) values ( 473036, 浙江华甸, 13442, 3, 10306, 2022, 1667443614864 ) 

一共插入了5条数据,后续可以根据company_id删除对应数据

在这里插入图片描述

t_r_orgination 组织表查询数据

在这里插入图片描述

select id, code, `name`, parent_id, `type`, company_id, product_id, create_time, update_time, ext1, ext2, ext3,person_count,im_group_id from t_r_orgination where id = 13442 

在这里插入图片描述

因为是查询操作,所以无需还原数据

t_r_orgination 组织表更新数据

在这里插入图片描述

update t_r_orgination SET code = 10306, `name` = 浙江华甸防雷科技股份有限公司, parent_id = 0, `type` = 1, company_id = 10306, product_id = 2022, create_time = 1667443614864, person_count = 1, im_group_id = 0 where id = 13442 

t_r_orgination 表查询数据

在这里插入图片描述

select id, code, `name`, parent_id, `type`, company_id, product_id, create_time, update_time, ext1, ext2, ext3,person_count,im_group_id from t_r_orgination where id = 0

从日志可以看出,没查到任何数据

t_r_res_sum_total 资料统计表插入数据

insert into t_r_res_sum_total (company_id, `type`, kind, kind_name, total, create_time, update_time, ext1, ext2, ext3) values (10306, 1, 1, 客户数量, 0, 1667443614902, null, null, null, null) , (10306, 2, 1, 关注数量, 0, 1667443614902, null, null, null, null) , (10306, 3, 1, 产品数量, 0, 1667443614902, null, null, null, null) , (10306, 4, 1, 线路数量, 0, 1667443614902, null, null, null, null) , (10306, 5, 1, 地址数量, 0, 1667443614902, null, null, null, null) , (10306(, 6, 1, 运力数量, 0, 1667443614902, null, null, null, null) , (10306, 7, 1, 司机数量, 0, 1667443614902, null, null, null, null) , (10306, 8, 1, 押运员数量, 0), 1667443614902, null, null, null, null) , (10306, 9, 1, 车头数量, 0, 1667443614902, null, null, null, null) , (10306, 10, 1, 挂车数量, 0, 1667443614902, null, null, null, null) 

一共插入了10条数据,后续可以根据company_id删除数据

在这里插入图片描述

t_o_product 表查询数据

在这里插入图片描述

通过日志可以看出,没有任何参数,所以未查出数据

结束 执行公共实现类

在这里插入图片描述

调用操作日志记录接口

在这里插入图片描述

t_r_operator_log 操作日志表写入数据

insert into t_r_operator_log ( order_id, code, `type`, remark, company_id, user_id, create_time ) values ( 473036, PR001, 1, 【企业注册】手机号:13912300000 用户Id:473036, 10000, 429381, 1667443614908 ) 

插入数据如下,后续可以根据company_id删除数据

在这里插入图片描述

t_r_orgination 组织表查询数据

在这里插入图片描述

Preparing: select id, code, `name`, parent_id, `type`, company_id, product_id, create_time, update_time, ext1, ext2, ext3,person_count,im_group_id from t_r_orgination WHERE company_id = 10306 and code = 10306 and `type` = 1 order by create_time desc,id desc limit 1

t_r_orgination 组织表更新数据

在这里插入图片描述

update t_r_orgination SET code = 10306, `name` = 浙江华甸防雷科技股份有限公司, parent_id = 0, `type` = 1, company_id = 10306, product_id = 2022, create_time = 1667443614864, update_time = 1667443614915, person_count = 1, im_group_id = 111150 where id = 13442 

更新数据如下,后续可以根据company_id删除数据
在这里插入图片描述

t_r_qualification_alert_config 资质告警资料配置表插入数据

在这里插入图片描述

一共插入了21条数据,插入数据如下,后续可以根据company_id删除数据
在这里插入图片描述

2.设计删除策略

从上述的日志分析过程可以看出,添加企业这个动作,一共涉及了15张数据表的查询、插入、更新和删除操作,其中14张表涉及到数据插入。查询就不管了,只分析哪几张表写入了哪些数据以及如何删除即可。经过梳理总结,得出:

t_r_company_base 表写入1条数据,可根据phone_number查询到id和user_id,这一步最关键,这个id实际就是其他数据表所用到的company_id,可通过phone_number删除;
t_r_user_info 表写入1条数据,可通过user_id删除;
t_r_account 表写入1条数据,可通过user_id删除;
t_r_user_post 表写入2条数据,可通过user_id删除;
t_r_user_company 表写入1条数据,可通过company_id删除;
t_r_msds_company 表写入1条数据,可通过code(company_id)删除;
t_r_company_extend 表写入2条数据,可通过company_id删除;
t_r_orgination 表共写入5条数据,可通过company_id删除;
t_r_location 表写入1条数据,可通过company_id删除;
t_r_transport_line 表写入2条数据,可通过company_id删除;
t_r_res_sum_total 表共写入10条数据,可通过company_id删除;
t_r_operator_log 表写入1条数据,可通过company_id删除;
t_r_qualification_alert_config 表共写入21条数据,可通过company_id删除;
t_r_transport_line_point 表共写入4条数据,这个处理稍微繁琐一点,先从t_r_transport_line表中根据company_id查到id,该id也就是t_r_transport_line_point表中的line_id,再根据line_id进行删除;

3.编写删除数据的SQL语句

SQL语句如下,将其直接封装到一个静态方法delete_company_data中,直接在测试用例中调用即可,这样做的好处是:

1、可以避免测试用例代码较多,只需调用该方法、传入一个企业联系人手机号和一个数据库实例;

2、即使后面添加企业接口发生变更,产生了更多关联数据,也只需修改本方法,而无需修改测试用例;

@staticmethod
def delete_company_data(use_db, phone_number):
    """
    删除新增企业产生的相关数据
    :param use_db: 数据库实例
    :param phone_number: 企业联系人手机号
    :return:
    """
    # 查询企业ID
    select_add_company_id = f"SELECT id FROM t_r_company_base WHERE `contact_phone`={phone_number};"
    # 查询用户ID
    select_add_user_id = f"SELECT user_id FROM t_r_company_base WHERE `contact_phone`={phone_number};"
    add_company_id = use_db.execute_sql(select_add_company_id)[0]
    add_user_id = use_db.execute_sql(select_add_user_id)[0]
    # 查询线路ID
    select_line_id = f"SELECT id FROM t_r_transport_line WHERE `company_id`={add_company_id};"
    line_ids = use_db.execute_sql(is_fetchall=True, sql=select_line_id)
    line_id_1 = line_ids[0][0]
    line_id_2 = line_ids[1][0]
    # 根据company_id删除资质告警资料配置
    delete_sql_1 = f"DELETE FROM t_r_qualification_alert_config WHERE company_id={add_company_id};"
    # 根据company_id删除组织表相关数据
    delete_sql_2 = f"DELETE FROM t_r_orgination WHERE company_id={add_company_id};"
    # 根据company_id删除操作日志
    delete_sql_3 = f"DELETE FROM t_r_operator_log WHERE company_id={add_company_id};"
    # 根据company_id删除操作资料统计
    delete_sql_4 = f"DELETE FROM t_r_res_sum_total WHERE company_id={add_company_id};"
    # 删除途经点信息
    delete_sql_5 = f"DELETE FROM t_r_transport_line_point WHERE line_id={line_id_1} or line_id={line_id_2};"
    # 删除运输线路表数据
    delete_sql_6 = f"DELETE FROM t_r_transport_line WHERE company_id={add_company_id};"
    # 删除坐标表数据
    delete_sql_7 = f"DELETE FROM t_r_location WHERE company_id={add_company_id};"
    # 删除企业扩展表数据
    delete_sql_8 = f"DELETE FROM t_r_company_extend WHERE company_id={add_company_id};"
    # 删除人员客商关系表数据
    delete_sql_9 = f"DELETE FROM t_r_user_company WHERE company_id={add_company_id};"
    # 删除人员岗位表数据
    delete_sql_10 = f"DELETE FROM t_r_user_post WHERE user_id={add_user_id};"
    # 删除账户表数据
    delete_sql_11 = f"DELETE FROM t_r_account WHERE user_id={add_user_id};"
    # 删除用户信息表数据
    delete_sql_12 = f"DELETE FROM t_r_user_info WHERE user_id={add_user_id};"
    # 删除企业物料绑定关系数据
    delete_sql_13 = f"DELETE FROM t_r_msds_company WHERE code={add_company_id};"
    # 删除企业基础表数据
    delete_sql_14 = f"DELETE FROM t_r_company_base WHERE id={add_company_id};"
    # 执行各个SQL
    use_db.execute_sql(delete_sql_1)
    use_db.execute_sql(delete_sql_2)
    use_db.execute_sql(delete_sql_3)
    use_db.execute_sql(delete_sql_4)
    use_db.execute_sql(delete_sql_5)
    use_db.execute_sql(delete_sql_6)
    use_db.execute_sql(delete_sql_7)
    use_db.execute_sql(delete_sql_8)
    use_db.execute_sql(delete_sql_9)
    use_db.execute_sql(delete_sql_10)
    use_db.execute_sql(delete_sql_11)
    use_db.execute_sql(delete_sql_12)
    use_db.execute_sql(delete_sql_13)
    use_db.execute_sql(delete_sql_14)

4.本地调试

前面查询日志过程中已经手动添加了一个企业,手机号为13213213132,这里来验证上面定义的删除策略能否成功删除数据。直接在测试用例类中新增一条用例,引用删除数据方法,执行SQL。

def test_1111(self, use_db, rs_resource):
    rs_resource.delete_company_data(use_db=use_db, phone_number=13213213132)

执行结果如下,不过这个方法并没有定义执行SQL后打印任何内容,所以在执行完成后只是正常运行没报错,看不出来是否成功删除了数据,后面还存在优化空间。

在这里插入图片描述

我通过手动查询各个数据表,确认各个关联数据均已删除。再次选择同一条企业数据进行新增时,依然能新增成功。

5.改造测试用例

本地调试通过后,即可改造测试用例中的逻辑。改造内容如下:

@pytest.mark.rs_smoke
@allure.story("企业管理")
def test_06_company_manager(self, rs_resource, rs_admin_login, rs_get_admin_user_info, use_db):
    """测试企业增改查接口"""
    user_id = rs_admin_login
    cpy_id = rs_get_admin_user_info
    # 此处去除读取ini配置文件逻辑
    # 随便传入一个页码,就算每次都从这一页开始,后面也会删除数据,不会导致数据重复
    page = 120
    get_company_list = rs_resource.get_company_list(cpy_id, user_id, page, 10)
    company_list = get_company_list["d"]
    company_name = company_list[0]["a"]
    company_simple = company_name[0:5]
    company_num = company_list[0]["d"]
    company_manager = self.fake.name()
    company_phone = self.fake.phone_number()
    company_pwd = 123456
    company_type = 3
    sort_id = str(random.randint(1, 100))

    try:
        logger.info(f"新增企业'{company_name}'信息...")
        with allure.step("调用添加企业接口"):
            add_company = rs_resource.add_company(cpy_id, user_id, company_name, company_manager, company_phone,
                                                  company_pwd, company_simple, company_num, company_type)
            while add_company["a"] == 254:
                # 以防万一,还是加入了页码自增+1逻辑,防止这一页的数据被手动用过
                page = page + 1
                get_company_list = rs_resource.get_company_list(cpy_id, user_id, page, 10)
                company_list = get_company_list["d"]
                company_name = company_list[0]["a"]
                company_simple = company_name[0:5]
                company_num = company_list[0]["d"]
                add_company = rs_resource.add_company(cpy_id, user_id, company_name, company_manager, company_phone,
                                                      company_pwd, company_simple, company_num, company_type)
        assert add_company["a"] == 200
        self.company_user_id = add_company["d"]
        select_db = use_db.execute_sql(
            f"SELECT * FROM t_r_company_base WHERE user_id = {self.company_user_id}")  # 查询数据库是否存在新增的数据
        assert company_name in str(select_db)
        logger.info(f"企业'{company_name}'新增信息成功")

        logger.info(f"修改企业'{company_name}'信息...")
        with allure.step("调用修改企业信息接口"):
            select_db = use_db.execute_sql(
                f"SELECT id FROM t_r_company_base WHERE user_id = {self.company_user_id}")  # 查询新增的数据的id
            self.company_id = int(select_db[0])
            modify_company = rs_resource.modify_company(cpy_id, user_id, self.company_id, company_name,
                                                        company_simple, company_manager)
        assert modify_company["a"] == 200
        logger.info(f"修改企业'{company_name}'信息成功")

        logger.info(f"修改企业'{company_name}'账号...")
        with allure.step("调用修改企业账号接口"):
            company_new_phone = self.fake.phone_number()
            modify_company_phone = rs_resource.modify_company_phone(cpy_id, user_id, self.company_user_id,
                                                                    self.company_id, company_new_phone)
        assert modify_company_phone["a"] == 200
        logger.info(f"修改企业'{company_name}'账号成功")

        logger.info(f"修改企业'{company_name}'密码...")
        with allure.step("调用修改企业密码接口"):
            modify_company_pwd = rs_resource.modify_company_pwd(cpy_id, user_id, self.company_user_id,
                                                                self.company_id, company_new_phone,
                                                                company_pwd='654321')
        assert modify_company_pwd["a"] == 200
        logger.info(f"修改企业'{company_name}'密码成功")

        logger.info(f"修改企业'{company_name}'排序...")
        with allure.step("调用修改企业排序接口"):
            modify_company_sort = rs_resource.modify_company_sort(cpy_id, user_id, self.company_id, sort_id)
        assert modify_company_sort["a"] == 200
        logger.info(f"修改企业'{company_name}'排序成功")

        logger.info(f"查询企业'{company_name}'信息...")
        with allure.step("调用查询企业信息接口"):
            query_company = rs_resource.query_company(cpy_id, user_id, company_num, company_type)
        assert query_company["a"] == 200
        logger.info(f"查询企业'{company_name}'信息成功")

        logger.info(f"给企业'{company_name}'绑定物料...")
        with allure.step("调用企业绑定物料接口"):
            get_exist_product_list = rs_resource.get_exist_product_list(cpy_id, user_id, x=1, y=1000)
            exist_product_list = get_exist_product_list["d"]
            exist_productID_list = []
            for i in exist_product_list:
                aa = i["aa"]
                exist_productID_list.append(aa)
            exist_product_id = random.choice(exist_productID_list)
            add_company_product = rs_resource.add_company_product(cpy_id, user_id, self.company_id, company_type,
                                                                  exist_product_id)
        assert add_company_product["a"] == 200
        select_db = use_db.execute_sql(
            f"SELECT * FROM t_r_msds_company WHERE code = {self.company_id}")  # 查询数据库是否存在新增的数据
        assert str(exist_product_id) in str(select_db)
        logger.info(f"企业'{self.company_id}'和物料的绑定关系成功")

        logger.info("删除新增企业产生的相关数据")
        rs_resource.delete_company_data(use_db=use_db, phone_number=company_new_phone)

    except AssertionError as e:
        logger.info(f"企业'{company_name}'增改查失败")
        raise e

6.运行测试

测试用例运行通过,改造完成。
在这里插入图片描述

总结

以上就是结合实际自动化测试案例,对数据恢复的思考和改造实践。下面简单总结一下此次改造过程中的一些心得:

  • 配置文件最好写在py文件中,避免单独定义一个方法来读取ini、yml等配置文件,从而加大出错概率;

  • 配置文件最好不经常变动,更不要在执行过程中更改(写入)配置文件,避免带来git协同时的重复提交问题;

  • 自动化测试设计与编码实现同样重要,不仅要保证业务流程能正常运行,还要保证设计的合理性和健壮性;

  • 测试人员不仅要熟悉系统业务流程,还要熟悉后台实现流程,即一个手动操作或接口执行的背后究竟做了哪些动作,如执行了哪些类的哪些方法,查询、写入、更新、删除了哪些表的哪些数据,这个过程可以通过阅览开发代码和后台日志查看和梳理;

  • 不要怕麻烦,越是复杂的地方可能越会有不一样的收获,比如我原本以为新增一个企业就是插入一两张表、几条数据这么简单,但实际上它背后涉及了十几张表的关联查询、插入、更新和删除;

当然,以上并不一定就是最优的设计,还存在诸多优化空间,如果你有更好的方案,欢迎留言交流!

如果你不想一个人野蛮生长,找不到系统的资料,问题得不到帮助,坚持几天便放弃的感受的话,可以加入我们的QQ群:746506216,大家可以一起讨论交流,里面会有各种软件测试资料和技术交流。


资源分享

下方这份完整的软件测试视频学习教程已经上传CSDN官方认证的二维码,朋友们如果需要可以自行免费领取 【保证100%免费】

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/9776.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2022 年 10 月区块链操作系统的开发回顾

查看 Cartesi Machine、Cartesi Rollups 和 Noether 的更新正在寻找区块链操作系统组件的最新进展&#xff1f;你找对地方了&#xff01;正如在我们的路线图文章中所描述的那样&#xff0c;我们一直在朝着定期且频繁的更新方向发展着&#xff0c;以便让我们的社区能够及时的了解…

【前端】Ajax

目录 一、服务器对外提供了哪些资源 二、了解Ajax 2.1什么是Ajax 2.2Ajax作用 三、jQuery中的Ajax 3.1$.get()函数的语法 3.2$.post()函数的语法 3.3$.ajax()函数的语法 四、接口 4.1接口的概念 4.2接口测试工具 4.3接口文档 一、服务器对外提供了哪些资源 如果要在…

1.1小程序内置tabbar和自定义tabbar区别

文章目录内置tabbar自定义 tabbar创建文件夹 custom-tab-bar发现小程序自定义tabBar切换颜色总是比点击慢一步switchtab报错“switchTab:fail page “pages/home/pages/message/message” is not found”直接在微信开发文档内搜索&#xff0c;出现的是自定义 tabbar &#xff0…

用HTML+CSS做一个简单好看的校园社团网页

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 校园班级网页设计 | 我的班级网页 | 我的学校 | 校园社团 | 校园运动会 | 等网站的设计与制作 | HTML期末大学生网页设计作业 HTML&#xff1a;结构 …

C# 零基础搭建一个简单的Asp.Net Core WebAip

下面介绍的vs2022 基于.NET 6 搭建的一个简单的Asp.Net Core WebAip 一、创建aps.net core Web Aip 1、创建一个新的Asp.Net Core WebApi 项目 启动vs2022&#xff0c;在开始页面选择“创建新项目(N)”。 选择从C#的asp.net core WbeApi 作为一个初始的WebApi模板。 点击下…

C++初阶 List的介绍和使用

作者&#xff1a;小萌新 专栏&#xff1a;初阶C 作者简介&#xff1a;大二学生 希望能和大家一起进步 博客简介&#xff1a;本篇博客会简单介绍List和它的用法 List的介绍和使用List介绍List的使用方式List的定义方式List的插入与删除push_front pop_frontpush_back pop_backi…

虚拟机磁盘扩容(纯命令行)

背景&#xff1a;磁盘使用率达到100%&#xff0c;无大数据文件可删除&#xff0c;需要进行磁盘扩容 步骤&#xff1a; 1.虚拟机调高分配给磁盘的大小 2.启动虚拟机&#xff0c;查看磁盘是否扩容 fdisk -l 3.扩容磁盘分区 fdisk /dev/sda 先输入p查看当前情况 输入d&#xf…

Flash、eeprom、rom、ram

存储器 ramrom rom: 不能编程prom: 可以写入一次eprom: 多次擦写, 需要在紫外线照一下eeprom: 任意修改 狭义EEPROM:广义EEPROM: flash nor flash: 字节读, 块擦除nand flash: 页读取, 块擦除 ROM&RAM EEPROM 可随机访问/修改任意字节, 可向每个bit写入0/1, 掉电不丢…

【附源码】计算机毕业设计JAVA传统文化知识竞赛系统

【附源码】计算机毕业设计JAVA传统文化知识竞赛系统 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA…

推荐一本计算机领域可以发的2区计算机智能医疗类SCI

本期小编给大家推荐的是我处新增的一本TECH SCIENCE PRESS旗下2区智能医疗类SCI. 这本期刊是一本经同行评审的开放获取期刊&#xff0c;出版计算机网络、人工智能、大数据、软件工程、多媒体、网络安全、物联网、材料基因组、集成材料科学以及数据分析、建模、现代功能和多功…

同花顺_代码解析_五彩K线

本文通过对同花顺自带的五彩K线进行解析&#xff0c;用以了解同花顺相关策略设计的思想。 目录 早晨之星 黄昏之星 十字星 长十字星 垂死丁字 射击之星 穿头破脚 红三兵 三只乌鸦 光头阳线 红绿灯 早晨之星 行号 1 a1 -> K线实体长度是开盘价的0.5%以下&…

MAX25————用vray还原模型在Substance Painter的光照以及材质效果

现在很多小伙伴喜欢在Substance Painter&#xff08;俗称SP&#xff09;里面做贴图。但是经常很苦恼&#xff0c;sp里做出的效果&#xff0c;导出来&#xff0c;放到unity或者max里面再渲染&#xff0c;光照跟材质效果就都变了。其实很简单&#xff0c;做到以下几点。就可以还原…

我的有趣的英语学习经历

文 / 谷雨&#xff08;微信公众号&#xff1a;王不留&#xff09; 我是一个社牛&#xff0c;大大咧咧&#xff0c;与人交往&#xff0c;不会瞻前顾后。 上半年&#xff0c;大四即将毕业&#xff0c;我闲来无事&#xff0c;突然脑袋发热&#xff0c;想找原版《经济学人》翻翻。于…

第十一届蓝桥杯国赛C++B组题解(A - J)

第十一届蓝桥杯国赛CB组 美丽的2 题目地址&#xff1a;https://www.lanqiao.cn/problems/1018/learning/ 难度&#xff1a;简单 知识点&#xff1a; 模拟枚举 【题目描述】 ​ 1−20201-20201−2020 中有多少个数中含有数字2 【解题思路】 范围很小&#xff0c;直接暴力判断…

c盘垃圾太多怎么清理?c盘垃圾太多需要重装系统嘛?

一台电脑为了方便大家平时使用过程中存储各种文件&#xff0c;会为用户们规划多个不同的磁盘&#xff0c;当然这些磁盘大家也可以使用一些工具来自己设置&#xff0c;其中最为重要的就是C盘了&#xff0c;作为系统磁盘C盘里面会存储大量的文件&#xff0c;这些文件并不是系统必…

思必驰周强:AI 和传统信号技术在实时音频通话中的应用

如何用 AI 解决声音传输&处理中的三大问题&#xff1f;三大问题又是哪三大问题&#xff1f; 在「RTE2022 实时互联网大会」中&#xff0c;思必驰研发总监 周强以《AI 和传统信号技术在实时音频通话中的应用》为题进行了主题演讲。 本文内容基于演讲内容进行整理&#xff0…

MySql表的基本增删改查详解

目录创建表create表中--插入数据--“增”单行&#xff0c;全列插入多行插入插入重复则-更新插入重复则-替换插入查询结果表中--检索数据--“查”全列查询指定列查询查询字段为表达式给查询结果指定别名WHERE 限定条件比较运算符逻辑运算符where使用实例模糊匹配ORDER BY排序筛选…

语音分类入门案例: 英文数字音频分类

语音分类入门案例: 英文数字音频分类本项目是一个全流程的语音分类项目&#xff0c;内容简单&#xff0c;适合想要涉猎音频分类的小白学习。 推荐将本项目Fork成为自己的项目并运行&#xff0c;以获得更好的学习体验!! 项目地址&#xff1a;语音分类入门&#xff1a;全流程英文…

浅谈企业的数据安全体系建设难点

随着云计算、大数据、物联网、移动互联网、人工智能等新技术的发展&#xff0c;网络边界被不断打破&#xff0c;数字双生、敏捷创新、安全合规驱动快速转型&#xff0c;社会和企业都在面临数字化的转型带来的数据安全风险。 近年来数据泄露的安全事件频发&#xff0c;国家和机…

基于混合整数遗传算法的最优成分选择(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…