python接口自动化框架_初级

1、前提:

  • python基础(能看懂代码就行,学到面向对象)
  • 找一个应用场景(比如在聚合数据中找一个测试接口:https://www.juhe.cn/)
  • Pycharm开发环境(IDE)

 

2、架子搭建:

  总说框架框架不够接地气,那就简单的理解为目录,如下图是我的目录:

  python接口自动化框架_初级_第1张图片

  这是一般通用性工程目录结构,

  • config目录管理所有的配置文件,里面可能有多个配置文件,类型可能是xml、ini、jsp等......。
  • help目录中一般我会放一些帮助理解的东西
  • public目录顾名思义,里面放一些公共类,比如日志管理、配置文件读取、数据库读取等......。
  • results目录执行后的测试报告与对应log。
  • src目录对所有测试用例系统调度执行。
  • test_case目录管理所有接口测试用例(unittest配合)
  • test_case_data目录管理所有测试用例需要的数据
  • README说明,这是一个习惯性的东西

3、模块

  在大脑中应该有一个基本的初型,可能会用到哪些模块,以下是需要用到的模块,淡定,下面会一一讲到:

  • requests:HTTP协议库
  • cx_Oracle:python访问oracle扩展模块
  • configparser:python操作配置文件
  • xlrd:操作excel读写
  • logging:python日志库
  • threading:线程模块
  • json:json模块
  • unittest:单元测试模块
  • paramunittest:参数化单元测试数据
  • HTMLTestRunner_PY3:结构单元测试模块生成HTML测试报告,本不支持PY3,需要修改原码

  其中只有cx_Oracle和HTMLTestRunner_PY3容易出错,都不是通过pip install xxx来进行安装的,如下链接查看:

  • cx_Oracle配置
  • HTMLTestRunner_PY3

 


 

4、入手

  把工程的基本目录结构搭建完后,一般想到的第一个问题是,应该从那里入手。那一般情况下,我会从公共类入手:

  4-1、比如ReadConfig(配置文件读取),在config中建一个config.ini的文件用来存放配置文件,我们用过很多C\S客户端类软件,都有一个类似的config,应该先想好把哪些内容放到配置文件中:

  python接口自动化框架_初级_第2张图片

  4-2、在public中新建ReadConfig.py(代码【需要代码的可移步到】如下),用来读取配置文件,使用到模块:configparser,可自行查找使用方法,强调一下,写完一个类,都应该在:main里测试一下,保存单个功能是正常的。如此也有阶段性成就感:

  python接口自动化框架_初级_第3张图片

  4-3、公共类Log.py,这是所有项目必须的公共类,在必要的地方记录log有助于查找问题所在,使用到的模块为logging,一般新手都有同一个问题:我知道应该打log,但log怎么打,打在哪里,打些什么东西呢?

  log记录内容不应该太过单一,可以在必要的步骤结点添加log,也可以在try except里打印error,也可打印一个用例的结果数据等等,只要有助你查找问题,查看流程都可以。

  python接口自动化框架_初级_第4张图片

  4-4、这里要说明一下,其实公共类编写没有一定的顺序,只是我一般会先写config,因为log里可能用到config;再写log,一般其它公共类也会打log。ReadConfig.py和Log.py写完后, 再想有哪些是接口测试需要用到的方法或参数。我想到一个,接口测试时,测试数据来源是哪里,难倒每次先手工测一下拿到数据写到我们的test_case_data里吗?那有些数据是动态变化的怎么办。这时就应该从用户的角度去想这个问题,哪些数据是可以从用户的角度去拿到的。根据用户能拿到的数据再去查找我们需要的数据,比如:接口post data中有一个参数:user_id,那我们应该从用户的角度去拿到用户名,再通过数据库查询到user_id,这时,就需要写一个数据库操作类,一般只需要查询,也可能把类完善到增删改查。

  python中对mysql和Oracle都有相应的模块支持,Mysql相对简单一些,我使用的Oracle支持模块cx_Oracle,模块安装如上所示,

  python接口自动化框架_初级_第5张图片

  python接口自动化框架_初级_第6张图片

  4-5、终于可能写到HTTP公共类啦,此类的作用是:初始化url、headers、data、get请求、post请求等,编写完后在main中测试一下get、post发送是否正常:

  python接口自动化框架_初级_第7张图片

  python接口自动化框架_初级_第8张图片

  4-6、还有一个get_data.py的公共文件,我会放一些现在没想到,之后需要的公共方法(比如:获取excel数据,获取请求的json数据,获取cookie\session数据),还有一些真实业务场景需要的方法:

  python接口自动化框架_初级_第9张图片

  python接口自动化框架_初级_第10张图片

  4-7、别忘记把HTMLTestRunner_PY3.py复制到public目录下,后期需要进行调试,到此,我用到的公共类、方法就写完了。


 

5、测试用例

  写完公共类后,再从宏观的想想整个测试流程:手工测试时,我们会写到测试用例(那种excel的形式),这里把表中一条条的数据作为输入,经过公共类处理,传给test_case,run执行入口负责调试所有test_case并执行,再把执行结果以html、excel、log的形式输出:如图所示:

  python接口自动化框架_初级_第11张图片

  从excel中一行行的数据执行,一定就有参数化的问题,需要unittest自己循环去执行,此时python构造器的优势就体现出来啦:使用在类前修饰:

  @paramunittest.parametrized(*instrBackRecordJsonList_xls),详见代码:
  注:要求与excel中的列一一对应
  
复制代码
import unittest
import paramunittest
from public import ReadConfig
from public.Log import MyLog
from public.MyHttp import Http
from public.GetData import get_xls
from public.OracleOperation import MyOracle
from public.GetData import get_cookie
from public.GetData import get_until

rc = ReadConfig.ReadConfig("808_config.ini")
h = Http()
orc = MyOracle()
instrBackRecordJsonList_xls = get_xls("instruct_data.xlsx", "instruct")
print(instrBackRecordJsonList_xls)


@paramunittest.parametrized(*instrBackRecordJsonList_xls)
class TestInstructTypeParam(unittest.TestCase):
    def setParameters(self, case_name, method, vehicleIds, cmdCode, cmdVal, sendTitle, paramCode, paramName, Id):
        self.case_name = str(case_name)
        self.method = str(method)
        self.vehicleIds = str(vehicleIds)
        self.cmdCode = str(cmdCode)
        self.cmdVal = str(cmdVal)
        self.sendTitle = str(sendTitle)
        self.paramCode = str(paramCode)
        self.paramName = str(paramName)
        self.Id = str(Id)
        self.return_json = None
        self.result_json = None
        self.session_id = get_cookie()

    def description(self):
        """
        描述
        :return:
        """
        self.case_name

    def setUp(self):
        """
        测试用例开始前布置环境,查询车辆的vehicleId,记录日志
        :return:
        """
        self.log = MyLog.get_log()
        self.logger = self.log.get_logger()
        sql = "SELECT VEHICLE_ID FROM V_VEHICLEINFO WHERE ID_NUMBER  LIKE '%s'" % rc.get_vehicle("ID_NUMBER")
        vid = orc.executeSQL(sql)
        self.vehicleIds = orc.get_one(vid)[0]
        print(self.vehicleIds)

    def test_Instruct(self):
        # 1、设置url
        url = rc.get_interface_url("instructTypeParam")
        h.set_url(url)

        # self.session_id = "Thp26DXAKwSrjGroUXpFD9lPwbV698WQVYT0KPi6vKK72MEeAuGY!-1564142059"
        # 2、设置header
        header = {"ocde": "0", "username": "system", "password": "system%40123", "orgCode": "B", "JSESSIONID": self.session_id
                  , "TOPMENU": "%2Fhome.do"}
        h.set_headers(header)
        # 3、设置data,请求参数
        data = {"vehicleIds": self.vehicleIds, "cmdCode": self.cmdCode, "cmdVal": self.cmdVal,
                "sendTitle": self.sendTitle, "paramCode": self.paramCode, "paramName": self.paramName,
                "id": self.Id}
        h.set_data(data)
        # 4、发送请求
        res = h.post()
        if res.status_code == 200:
            self.return_json = h.post().json()
            print(self.return_json)
            # 5、检查结果
            # 先获取指令反馈结果,调用get_until()方法
            try:
                rollcallId = self.return_json["text"]
            except TypeError as e:
                self.logger.error(e)
            self.result_json = get_until(self.vehicleIds, rollcallId, 65, self.session_id)

            self.checkResult()
        else:
            self.logger.error("地址%s的Status Code:%s" % (url, res.status_code))

    def checkResult(self):
        if self.result_json:
            if self.result_json[0]["result"] == 0:
                msg = "指令(%s):成功!终端返回信息:[%s]" % (self.case_name, self.result_json[0]["backInfo"])
            elif self.result_json[0]["result"] == 1:
                msg = "指令(%s):超时!终端返回信息:[%s]" % (self.case_name, self.result_json[0]["backInfo"])
            else:
                msg = "指令(%s):未发送!终端返回信息:[%s]" % (self.case_name, self.result_json[0]["backInfo"])

            self.assertEqual(self.result_json[0]["result"], 0, msg=msg)

    def tearDown(self):
        # 用例执行后把self.return_json记录要日志中
        self.log.build_case_line(self.case_name, **self.return_json)

if __name__ == '__main__':
    # 在unittest.main()中加 verbosity 参数可以控制输出的错误报告的详细程度,默认是 1,
    # 如果设为 0,则不输出每一用例的执行结果,即没有上面的结果中的第1行;如果设为 2,则输出详细的执行结果

    unittest.main(verbosity=2)
复制代码

6、(run)执行

  写了多个test_case后,怎么把他们组装起来,比如,获取所有test_case,以一定的顺序去执行,或有些执行有些不执行,应该怎么去调度呢?

  建议多了解一下TestSuite测试集,在run中添加获取所有测试集的函数,最后run方法中拿到所有测试集,输出html报告,执行,打印log,finally中close()文件。代码如下:

  

复制代码
# -*- coding:utf-8 -*-
#__author:Administrator
#date: 2018/1/3

import os
import unittest
from public import ReadConfig
from public.Log import MyLog
from public import HTMLTestRunner_PY3


class RunTest:
    def __init__(self):
        """
        初始化需要的参数
        :return:
        """
        global log, logger, resultPath
        # log初始化
        log = MyLog.get_log()
        self.logger = log.get_logger()
        # 定义结果保存路径
        self.resultPath = log.get_report_path()
        # 取得config\caselist.txt文件路径
        self.caseListFile = os.path.join(ReadConfig.conf_path, "caselist.txt")
        # 取得test_case文件路径
        self.caseFile = os.path.join(ReadConfig.proDir, "test_case")
        # 定义一个空列表,用于保存类名
        self.caseList = []

    def get_case_list(self):
        """
        获取config\caselist.txt中的每一行,以#号开头的除外
        且添加到列表self.caseList进行返回
        :return: self.caseList
        """
        f = open(self.caseListFile)
        for value in f.readlines():
            if value != '' and not value.startswith("#"):
                self.caseList.append(value.replace("\n", ""))
        f.close()
        return self.caseList

    def get_case_suite(self):
        """
        获取测试集
        :return:
        """
        # 获取config\caselist.txt中的每一行
        self.get_case_list()
        # 定义测试集对象
        test_suite = unittest.TestSuite()
        # 初始化一个列表,存在所有的测试模块
        suite_module = []
        # 获取className ,把所有case中测试集添加到suite_module列表中
        for case in self.caseList:
            case_name = case.split("/")[-1]
            # print(case_name + ".py")
            discover = unittest.defaultTestLoader.discover(self.caseFile, pattern=case_name + ".py", top_level_dir=None)
            suite_module.append(discover)

        # 获取列表中所有的测试模块,添加到测试集test_suite中
        if len(suite_module) > 0:
            for suite in suite_module:
                for test_name in suite:
                    test_suite.addTest(test_name)
        else:
            return None
        return test_suite

    def run(self):
        try:
            # 获取测试集
            suit = self.get_case_suite()
            print("suit:", suit)
            # 判断测试集是否为None
            if suit is not None:
                self.logger.info("********TEST START********")
                f = open(self.resultPath, 'wb')
                # 使用HTMLTestRunner输出html报告
                runner = HTMLTestRunner_PY3.HTMLTestRunner(stream=f, title='Test Report',
                                                           description='Test Description', verbosity=2)
                # 运行测试用例
                runner.run(suit)
            else:
                self.logger.info("Have no case to test.")
        except Exception as e:
            self.logger.error(str(e))
        finally:
            self.logger.info("*********TEST END*********")
            f.close()


if __name__ == '__main__':

    testRun = RunTest()
    testRun.run()
复制代码

7、总结

  起初,不会面向对象的你,只能具体化代码;而后,刚学会面向对象的你,又把所有内容都抽象化,使得自己的架子越来越大,想的东西越来越多,希望一个架子能完成所有的功能。

  应该结合具体和抽象共同去完成一个功能,我把这个叫结构化抽象,抽象单一功能后就对应一个实例去测试下。。。。。。

  写的有问题的地方大牛们理解下,只是个人观点,欢迎大家指正!!!  

  我也是新手,在此记录下思路,希望和大家一起学习,

  

你可能感兴趣的:(python接口自动化框架_初级)