python+requests接口自动化测试框架实例详解

前段时间由于公司测试方向的转型,由原来的web页面功能测试转变成接口测试,之前大多都是手工进行,利用postman和jmeter进行的接口测试,后来,组内有人讲原先web自动化的测试框架移驾成接口的自动化框架,使用的是java语言,但对于一个学java,却在学python的我来说,觉得python比起java更简单些,所以,我决定自己写python的接口自动化测试框架,由于本人也是刚学习python,这套自动化框架目前已经基本完成了,于是进行一些总结,便于以后回顾温习,有许多不完善的地方,也遇到了许多的问题,希望大神们多多指教。下面我就进行今天的主要内容吧。

首先,我们先来理一下思路

正常的接口测试流程是什么?

脑海里的反应是不是这样的:

确定测试接口的工具 —> 配置需要的接口参数 —> 进行测试 —> 检查测试结果(有的需要数据库辅助) —> 生成测试报告(html报告)

那么,我们就根据这样的过程来一步步搭建我们的框架。在这个过程中,我们需要做到业务和数据的分离,这样才能灵活,达到我们写框架的目的。只要好好做,一定可以成功。这也是我当初对自己说的。

接下来,我们来进行结构的划分。

我的结构是这样的,大家可以参考下:

图片

  1.   common:存放一些共通的方法

  2.   result:执行过程中生成的文件夹,里面存放每次测试的结果

  3.   testCase:用于存放具体的测试case

  4.   testFile:存放测试过程中用到的文件,包括上传的文件,测试用例以及数据库的sql语句

  5.   caselist:txt文件,配置每次执行的case名称

  6.   config:配置一些常量,例如数据库的相关信息,接口的相关信息等

  7.   readConfig: 用于读取config配置文件中的内容

  8.   runAll:用于执行case

既然整体结构有了划分,接下来就该一步步的填充整个框架了,首先,我们先来看看config.ini和readConfig.py两个文件,从他们入手,个人觉得比较容易走下去哒。

我们来看下文件的内容是什么样子的:

  1. [DATABASE]

  2. host = 50.23.190.57

  3. username = xxxxxx

  4. password = ******

  5. port = 3306

  6. database = databasename

  7. [HTTP]

  8. # 接口的url

  9. baseurl = http://xx.xxxx.xx 

  10. port = 8080

  11. timeout = 1.0

  12. [EMAIL]

  13. mail_host = smtp.163.com

  14. mail_user = xxx@163.com

  15. mail_pass = *********

  16. mail_port = 25

  17. sender = xxx@163.com

  18. receiver = xxxx@qq.com/xxxx@qq.com

  19. subject = python

  20. content = "All interface test has been complited\nplease read the report file about the detile of result in the attachment."

  21. testuser = Someone

  22. on_off = 1

相信大家都知道这样的配置文件,没错,所有一成不变的东西,我们都可以放到这里来。哈哈,怎么样,不错吧。

现在,我们已经做好了固定的“仓库”。来保存我们平时不动的东西,那么,我们要怎么把它拿出来为我所用呢?这时候,readConfig.py文件出世了,它成功的帮我们解决了这个问题,下面就让我们来一睹它的庐山真面目吧。

  1. import os

  2. import codecs

  3. import configparser

  4. proDir = os.path.split(os.path.realpath(__file__))[0]

  5. configPath = os.path.join(proDir, "config.ini")

  6. class ReadConfig:

  7.     def __init__(self):

  8.         fd = open(configPath)

  9.         data = fd.read()

  10.         #  remove BOM

  11.         if data[:3] == codecs.BOM_UTF8:

  12.             data = data[3:]

  13.             file = codecs.open(configPath, "w")

  14.             file.write(data)

  15.             file.close()

  16.         fd.close()

  17.         self.cf = configparser.ConfigParser()

  18.         self.cf.read(configPath)

  19.     def get_email(self, name):

  20.         value = self.cf.get("EMAIL", name)

  21.         return value

  22.     def get_http(self, name):

  23.         value = self.cf.get("HTTP", name)

  24.         return value

  25.     def get_db(self, name):

  26.         value = self.cf.get("DATABASE", name)

  27.         return value

怎么样,是不是看着很简单啊,我们定义的方法,根据名称取对应的值,是不是so easy?!当然了,这里我们只用到了get方法,还有其他的例如set方法,有兴趣的同学可以自己去探索下

话不多说,我们先来看下common到底有哪些东西。

图片

既然配置文件和读取配置文件我们都已经完成了,也看到了common里的内容,接下来就可以写common里的共通方法了,从哪个下手呢?今天,我们就来翻“Log.py”的牌吧,因为它是比较独立的,我们单独跟他打交道,也为了以后它能为我们服务打下良好基础。

这里呢,我想跟大家多说两句,对于这个log文件呢,我给它单独启用了一个线程,这样在整个运行过程中,我们在写log的时候也会比较方便,看名字大家也知道了,这里就是我们对输出的日志的所有操作了,主要是对输出格式的规定,输出等级的定义以及其他一些输出的定义等等。总之,你想对log做的任何事情,都可以放到这里来。我们来看下代码,没有比这个更直接有效的了。

  1. import logging

  2. from datetime import datetime

  3. import threading

首先,我们要像上面那样,引入需要的模块,才能进行接下来的操作。

 
  1. class Log:

  2.     def __init__(self):

  3.         global logPath, resultPath, proDir

  4.         proDir = readConfig.proDir

  5.         resultPath = os.path.join(proDir, "result")

  6.         # create result file if it doesn't exist

  7.         if not os.path.exists(resultPath):

  8.             os.mkdir(resultPath)

  9.         # defined test result file name by localtime

  10.         logPath = os.path.join(resultPath, str(datetime.now().strftime("%Y%m%d%H%M%S")))

  11.         # create test result file if it doesn't exist

  12.         if not os.path.exists(logPath):

  13.             os.mkdir(logPath)

  14.         # defined logger

  15.         self.logger = logging.getLogger()

  16.         # defined log level

  17.         self.logger.setLevel(logging.INFO)

  18.         # defined handler

  19.         handler = logging.FileHandler(os.path.join(logPath, "output.log"))

  20.         # defined formatter

  21.         formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

  22.         # defined formatter

  23.         handler.setFormatter(formatter)

  24.         # add handler

  25.         self.logger.addHandler(handler)

现在,我们创建了上面的Log类,在__init__初始化方法中,我们进行了log的相关初始化操作。具体的操作内容,注释已经写得很清楚了(英文有点儿差,大家看得懂就行,嘿嘿……),这样,log的基本格式已经定义完成了,至于其他的方法,就靠大家自己发挥了,毕竟每个人的需求也不同,我们就只写普遍的共用方法啦。接下来,就是把它放进一个线程内了,请看下面的代码:

  1. class MyLog:

  2.     log = None

  3.     mutex = threading.Lock()

  4.     def __init__(self):

  5.         pass

  6.     @staticmethod

  7.     def get_log():

  8.         if MyLog.log is None:

  9.             MyLog.mutex.acquire()

  10.             MyLog.log = Log()

  11.             MyLog.mutex.release()

  12.         return MyLog.log

看起来是不是没有想象中的那样复杂啊,哈哈哈,就是这样简单,python比java简单了许多,这也是我为什么选择它的原因,虽然小编我也是刚刚学习,还有很多不懂的地方。

好了,至此log的内容也结束了,是不是感觉自己棒棒哒~其实,无论什么时候,都不要感到害怕,要相信“世上无难事只怕有心人”。

下面,我们继续搭建,这次要做的,是configHttp.py的内容。没错,我们开始配置接口文件啦!(终于写到接口了,是不是很开心啊~)

下面是接口文件中主要部分的内容,让我们一起来看看吧。

  1. import requests

  2. import readConfig as readConfig

  3. from common.Log import MyLog as Log

  4. localReadConfig = readConfig.ReadConfig()

  5. class ConfigHttp:

  6.     def __init__(self):

  7.         global host, port, timeout

  8.         host = localReadConfig.get_http("baseurl")

  9.         port = localReadConfig.get_http("port")

  10.         timeout = localReadConfig.get_http("timeout")

  11.         self.log = Log.get_log()

  12.         self.logger = self.log.get_logger()

  13.         self.headers = {}

  14.         self.params = {}

  15.         self.data = {}

  16.         self.url = None

  17.         self.files = {}

  18.     def set_url(self, url):

  19.         self.url = host + url

  20.     def set_headers(self, header):

  21.         self.headers = header

  22.     def set_params(self, param):

  23.         self.params = param

  24.     def set_data(self, data):

  25.         self.data = data

  26.     def set_files(self, file):

  27.         self.files = file

  28.     # defined http get method

  29.     def get(self):

  30.         try:

  31.             response = requests.get(self.url, params=self.params, headers=self.headers, timeout=float(timeout))

  32.             # response.raise_for_status()

  33.             return response

  34.         except TimeoutError:

  35.             self.logger.error("Time out!")

  36.             return None

  37.     # defined http post method

  38.     def post(self):

  39.         try:

  40.             response = requests.post(self.url, headers=self.headers, data=self.data, files=self.files, timeout=float(timeout))

  41.             # response.raise_for_status()

  42.             return response

  43.         except TimeoutError:

  44.             self.logger.error("Time out!")

  45.             return None

这里我们就挑重点来说吧。首先,可以看到,小编这次是用python自带的requests来进行接口测试的,相信有心的朋友已经看出来了,python+requests这个模式是很好用的,它已经帮我们封装好了测试接口的方法,用起来很方便。这里呢,我就拿get和post两个方法来说吧。(平时用的最多的就是这两个方法了,其他方法,大家可以仿照着自行扩展)

get方法

接口测试中见到最多的就是get方法和post方法,其中,get方法用于获取接口的测试,说白了,就是说,使用get的接口,都不会对后台数据进行更改,而且get方法在传递参数后,url的格式是这样的:

http://接口地址?key1=value1&key2=value2

对于requests提供的get方法,有几个常用的参数:

url:显而易见,就是接口的地址url啦

headers:定制请求头(headers),例如:content-type = application/x-www-form-urlencoded

params:用于传递测试接口所要用的参数,这里我们用python中的字典形式(key:value)进行参数的传递。

timeout:设置接口连接的最大时间(超过该时间会抛出超时错误)

现在,各个参数我们已经知道是什么意思了,剩下的就是往里面填值啦,是不是机械式的应用啊,哈哈,小编我就是这样机械般的学习的啦~

举个栗子:

  1. url=‘http://api.shein.com/v2/member/logout’

  2. header={‘content-type’:application/x-www-form-urlencoded}

  3. param={‘user_id’: 123456,‘email’: 123456@163.com}

  4. timeout=0.5

  5. requests.get(url, headers=header, params=param, timeout=timeout)

post方法

与get方法类似,只要设置好对应的参数,就可以了。下面就直接举个栗子,直接上代码吧:

  1. url=‘http://api.shein.com/v2/member/login’

  2. header={‘content-type’:application/x-www-form-urlencoded}

  3. data={‘email’: 123456@163.com,‘password’: 123456}

  4. timeout=0.5

  5. requests.post(url, headers=header, data=data, timeout=timeout)

怎么样,是不是也很简单啊。这里我们需要说明一下,post方法中的参数,我们不在使用params进行传递,而是改用data进行传递了。哈哈哈,终于说完啦,下面我们来探(了)讨(解)下接口的返回值。

依然只说常用的返回值的操作。

text:获取接口返回值的文本格式

json():获取接口返回值的json()格式

status_code:返回状态码(成功为:200)

headers:返回完整的请求头信息(headers['name']:返回指定的headers内容)

encoding:返回字符编码格式

url:返回接口的完整url地址

以上这些,就是常用的方法啦,大家可自行取之。

关于失败请求抛出异常,我们可以使用“raise_for_status()”来完成,那么,当我们的请求发生错误时,就会抛出异常。在这里提醒下各位朋友,如果你的接口,在地址不正确的时候,会有相应的错误提示(有时也需要进行测试),这时,千万不能使用这个方法来抛出错误,因为python自己在链接接口时就已经把错误抛出,那么,后面你将无法测试期望的内容。而且程序会直接在这里当掉,以错误来计。(别问我怎么知道的,因为我就是测试的时候发现的)

好了,快,我想学(看)习(看)common.py里的内容。

 
  1. import os

  2. from xlrd import open_workbook

  3. from xml.etree import ElementTree as ElementTree

  4. from common.Log import MyLog as Log

  5. localConfigHttp = configHttp.ConfigHttp()

  6. log = Log.get_log()

  7. logger = log.get_logger()

  8. # 从excel文件中读取测试用例

  9. def get_xls(xls_name, sheet_name):

  10.     cls = []

  11.     # get xls file's path

  12.     xlsPath = os.path.join(proDir, "testFile", xls_name)

  13.     # open xls file

  14.     file = open_workbook(xlsPath)

  15.     # get sheet by name

  16.     sheet = file.sheet_by_name(sheet_name)

  17.     # get one sheet's rows

  18.     nrows = sheet.nrows

  19.     for i in range(nrows):

  20.         if sheet.row_values(i)[0] != u'case_name':

  21.             cls.append(sheet.row_values(i))

  22.     return cls

  23. # 从xml文件中读取sql语句

  24. database = {}

  25. def set_xml():

  26.     if len(database) == 0:

  27.         sql_path = os.path.join(proDir, "testFile", "SQL.xml")

  28.         tree = ElementTree.parse(sql_path)

  29.         for db in tree.findall("database"):

  30.             db_name = db.get("name")

  31.             # print(db_name)

  32.             table = {}

  33.             for tb in db.getchildren():

  34.                 table_name = tb.get("name")

  35.                 # print(table_name)

  36.                 sql = {}

  37.                 for data in tb.getchildren():

  38.                     sql_id = data.get("id")

  39.                     # print(sql_id)

  40.                     sql[sql_id] = data.text

  41.                 table[table_name] = sql

  42.             database[db_name] = table

  43. def get_xml_dict(database_name, table_name):

  44.     set_xml()

  45.     database_dict = database.get(database_name).get(table_name)

  46.     return database_dict

  47. def get_sql(database_name, table_name, sql_id):

  48.     db = get_xml_dict(database_name, table_name)

  49.     sql = db.get(sql_id)

  50.     return sql

上面就是我们common的两大主要内容了,什么?还不知道是什么吗?让我告诉你吧。

  1. 我们利用xml.etree.Element来对xml文件进行操作,然后通过我们自定义的方法,根据传递不同的参数取得不(想)同(要)的值。

  2. 利用xlrd来操作excel文件,注意啦,我们是用excel文件来管理测试用例的。

听起来会不会有点儿懵,小编刚学时也很懵,看文件就好理解了。

excel文件:

图片

xml文件:

图片

至于具体的方法,我就不再一点点讲解了

接下来,我们看看数据库和发送邮件吧(也可根据需要,不写该部分内容)

先看老朋友“数据库”吧。

小编这次使用的是MySQL数据库,所以我们就以它为例吧。

  1. import pymysql

  2. import readConfig as readConfig

  3. from common.Log import MyLog as Log

  4. localReadConfig = readConfig.ReadConfig()

  5. class MyDB:

  6.     global host, username, password, port, database, config

  7.     host = localReadConfig.get_db("host")

  8.     username = localReadConfig.get_db("username")

  9.     password = localReadConfig.get_db("password")

  10.     port = localReadConfig.get_db("port")

  11.     database = localReadConfig.get_db("database")

  12.     config = {

  13.         'host': str(host),

  14.         'user': username,

  15.         'passwd': password,

  16.         'port': int(port),

  17.         'db': database

  18.     }

  19.     def __init__(self):

  20.         self.log = Log.get_log()

  21.         self.logger = self.log.get_logger()

  22.         self.db = None

  23.         self.cursor = None

  24.     def connectDB(self):

  25.         try:

  26.             # connect to DB

  27.             self.db = pymysql.connect(**config)

  28.             # create cursor

  29.             self.cursor = self.db.cursor()

  30.             print("Connect DB successfully!")

  31.         except ConnectionError as ex:

  32.             self.logger.error(str(ex))

  33.     def executeSQL(self, sql, params):

  34.         self.connectDB()

  35.         # executing sql

  36.         self.cursor.execute(sql, params)

  37.         # executing by committing to DB

  38.         self.db.commit()

  39.         return self.cursor

  40.     def get_all(self, cursor):

  41.         value = cursor.fetchall()

  42.         return value

  43.     def get_one(self, cursor):

  44.         value = cursor.fetchone()

  45.         return value

  46.     def closeDB(self):

  47.         self.db.close()

  48.         print("Database closed!")

这就是完整的数据库的文件啦。因为小编的需求对数据库的操作不是很复杂,所以这些已基本满足要求啦。注意下啦,在此之前,请朋友们先把pymysql装起来!pymysql装起来!pymysql装起来!(重要的事情说三遍),安装的方法很简单,由于小编是使用pip来管理python包安装的,所以只要进入python安装路径下的pip文件夹下,执行以下命令即可:

pip install pymysql

哈哈哈,这样我们就可以利用python链接数据库啦~

小伙伴们发现没,在整个文件中,我们并没有出现具体的变量值哦,为什么呢?没错,因为前面我们写了config.ini文件,所有的数据库配置信息都在这个文件内哦,是不是感觉很方便呢,以后就算变更数据库了,也只要修改config.ini文件的内容就可以了,结合前面测试用例的管理(excel文件),sql语句的存放(xml文件),还有接下来我们要说的,businessCommon.py和存放具体case的文件夹,那么我们就已经将数据和业务分开啦,哈哈哈,想想以后修改测试用例内容,sql语句神马的工作,再也不用每个case都修改,只要改几个固定的文件,是不是顿时开心了呢?

回归上面的configDB.py文件,内容很简单,相信大家都能看得懂,就是连接数据库,执行sql,获取结果,最后关闭数据库,没有什么不一样的地方。

该谈谈邮件啦,你是不是也遇到过这样的问题:每次测试完之后,都需要给开发一份测试报告。那么,对于我这样的懒人,是不愿意老是找人家开发的,所以,我就想,每次测试完,我们可以让程序自己给开发人员发一封email,告诉他们,测试已经结束了,并且把测试报告以附件的形式,通过email发送给开发者的邮箱,这样岂不是爽哉!

所以,configEmail.py应运而生。当当当当……请看:

 
  1. import os

  2. import smtplib

  3. from email.mime.multipart import MIMEMultipart

  4. from email.mime.text import MIMEText

  5. from datetime import datetime

  6. import threading

  7. import readConfig as readConfig

  8. from common.Log import MyLog

  9. import zipfile

  10. import glob

  11. localReadConfig = readConfig.ReadConfig()

  12. class Email:

  13.     def __init__(self):

  14.         global host, user, password, port, sender, title, content

  15.         host = localReadConfig.get_email("mail_host")

  16.         user = localReadConfig.get_email("mail_user")

  17.         password = localReadConfig.get_email("mail_pass")

  18.         port = localReadConfig.get_email("mail_port")

  19.         sender = localReadConfig.get_email("sender")

  20.         title = localReadConfig.get_email("subject")

  21.         content = localReadConfig.get_email("content")

  22.         self.value = localReadConfig.get_email("receiver")

  23.         self.receiver = []

  24.         # get receiver list

  25.         for n in str(self.value).split("/"):

  26.             self.receiver.append(n)

  27.         # defined email subject

  28.         date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

  29.         self.subject = title + " " + date

  30.         self.log = MyLog.get_log()

  31.         self.logger = self.log.get_logger()

  32.         self.msg = MIMEMultipart('mixed')

  33.     def config_header(self):

  34.         self.msg['subject'] = self.subject

  35.         self.msg['from'] = sender

  36.         self.msg['to'] = ";".join(self.receiver)

  37.     def config_content(self):

  38.         content_plain = MIMEText(content, 'plain', 'utf-8')

  39.         self.msg.attach(content_plain)

  40.     def config_file(self):

  41.         # if the file content is not null, then config the email file

  42.         if self.check_file():

  43.             reportpath = self.log.get_result_path()

  44.             zippath = os.path.join(readConfig.proDir, "result", "test.zip")

  45.             # zip file

  46.             files = glob.glob(reportpath + '\*')

  47.             f = zipfile.ZipFile(zippath, 'w', zipfile.ZIP_DEFLATED)

  48.             for file in files:

  49.                 f.write(file)

  50.             f.close()

  51.             reportfile = open(zippath, 'rb').read()

  52.             filehtml = MIMEText(reportfile, 'base64', 'utf-8')

  53.             filehtml['Content-Type'] = 'application/octet-stream'

  54.             filehtml['Content-Disposition'] = 'attachment; filename="test.zip"'

  55.             self.msg.attach(filehtml)

  56.     def check_file(self):

  57.         reportpath = self.log.get_report_path()

  58.         if os.path.isfile(reportpath) and not os.stat(reportpath) == 0:

  59.             return True

  60.         else:

  61.             return False

  62.     def send_email(self):

  63.         self.config_header()

  64.         self.config_content()

  65.         self.config_file()

  66.         try:

  67.             smtp = smtplib.SMTP()

  68.             smtp.connect(host)

  69.             smtp.login(user, password)

  70.             smtp.sendmail(sender, self.receiver, self.msg.as_string())

  71.             smtp.quit()

  72.             self.logger.info("The test report has send to developer by email.")

  73.         except Exception as ex:

  74.             self.logger.error(str(ex))

  75. class MyEmail:

  76.     email = None

  77.     mutex = threading.Lock()

  78.     def __init__(self):

  79.         pass

  80.     @staticmethod

  81.     def get_email():

  82.         if MyEmail.email is None:

  83.             MyEmail.mutex.acquire()

  84.             MyEmail.email = Email()

  85.             MyEmail.mutex.release()

  86.         return MyEmail.email

  87. if __name__ == "__main__":

  88.     email = MyEmail.get_email()

这里就是完整的文件内容了,不过可惜的是,小编我遇到一个问题

问题:使用163免费邮箱服务器进行邮件的发送,但是,每次发送邮件,都会被163邮件服务器退信,抛出的错误码是:554

官方说明如下:

图片

但是,however,but……小编在整合email进本框架之前写的发送email的小demo是可以正常发送邮件的。这个问题困扰着我,目前仍没有解决。

离成功不远了,什么?想知道生成测试报告的样子?好,这就满足好奇的你:

图片

好了,重头戏来了,就是我们的runAll.py啦。请看主角登场。

这是我们整个框架运行的入口,上面内容完成后,这是最后一步啦,写完它,我们的框架就算是完成了。(鼓掌,撒花~)

 
  1. import unittest

  2. import HTMLTestRunner

  3.     def set_case_list(self):

  4.         fb = open(self.caseListFile)

  5.         for value in fb.readlines():

  6.             data = str(value)

  7.             if data != '' and not data.startswith("#"):

  8.                 self.caseList.append(data.replace("\n", ""))

  9.         fb.close()

  10.     def set_case_suite(self):

  11.         self.set_case_list()

  12.         test_suite = unittest.TestSuite()

  13.         suite_model = []

  14.         for case in self.caseList:

  15.             case_file = os.path.join(readConfig.proDir, "testCase")

  16.             print(case_file)

  17.             case_name = case.split("/")[-1]

  18.             print(case_name+".py")

  19.             discover = unittest.defaultTestLoader.discover(case_file, pattern=case_name + '.py', top_level_dir=None)

  20.             suite_model.append(discover)

  21.         if len(suite_model) > 0:

  22.             for suite in suite_model:

  23.                 for test_name in suite:

  24.                     test_suite.addTest(test_name)

  25.         else:

  26.             return None

  27.         return test_suite

  28.     def run(self):

  29.         try:

  30.             suit = self.set_case_suite()

  31.             if suit is not None:

  32.                 logger.info("********TEST START********")

  33.                 fp = open(resultPath, 'wb')

  34.                 runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title='Test Report', description='Test Description')

  35.                 runner.run(suit)

  36.             else:

  37.                 logger.info("Have no case to test.")

  38.         except Exception as ex:

  39.             logger.error(str(ex))

  40.         finally:

  41.             logger.info("*********TEST END*********")

  42.             # send test report by email

  43.             if int(on_off) == 0:

  44.                 self.email.send_email()

  45.             elif int(on_off) == 1:

  46.                 logger.info("Doesn't send report email to developer.")

  47.             else:

  48.                 logger.info("Unknow state.")

上面我贴出了runAll里面的主要部分,首先我们要从caselist.txt文件中读取需要执行的case名称,然后将他们添加到python自带的unittest测试集中,最后执行run()函数,执行测试集。

终于呢,整个接口自动化框架已经讲完了,大家是不是看明白了呢?什么?之前的之前贴出的目录结构中的文件还有没说到的?嘿嘿,,,相信不用小编多说,大家也大概知道了,剩下文件夹的作用了。嗯~思索万千,还是决定简单谈谈吧。直接上图,简单明了:

图片

result文件夹会在首次执行case时生成,并且以后的测试结果都会被保存在该文件夹下,同时每次测试的文件夹都是用系统时间命名,里面包含了两个文件,log文件和测试报告。

图片

testCase文件夹下,存放我们写的具体的测试case啦,上面这些就是小编写的一些。注意喽,所有的case名称都要以test开头来命名哦,这是因为,unittest在进行测试时会自动匹配testCase文件夹下面所有test开头的.py文件

图片

testFile文件夹下,放置我们测试时用来管理测试用例的excel文件和用于数据库查询的sql语句的xml文件哦。

最后就是caselist.txt文件了,就让你们瞄一眼吧:

图片

凡是没有被注释掉的,都是要被执行的case名称啦。在这里写上你要执行的case名称就可以啦。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端:改变鼠标点击物体的颜色

需求&#xff1a; 需要改变图片中某一物体的颜色&#xff0c;该物体是纯色&#xff1b; 鼠标点击哪个物体&#xff0c;哪个物体的颜色变为指定的颜色&#xff0c;利用canvas实现。 演示案例 代码Demo <!DOCTYPE html> <html lang"en"><head>&l…

聊一聊 C#前台线程 如何阻塞程序退出

一&#xff1a;背景 1. 讲故事 这篇文章起源于我的 C#内功修炼训练营里的一位朋友提的问题&#xff1a;后台线程的内部是如何运转的 ? &#xff0c;犹记得C# Via CLR这本书中 Jeffery 就聊到了他曾经给别人解决一个程序无法退出的bug&#xff0c;最后发现是有一个 Backgrond…

Nmap基础入门及常用命令汇总

Nmap基础入门 免责声明&#xff1a;本文单纯分享技术&#xff0c;请大家使用过程中遵守法律法规~ 介绍及安装 nmap是网络扫描和主机检测的工具。作为一个渗透测试人员&#xff0c;必不可少的就是获取信息。那么nmap就是我们从互联网上获取信息的途径&#xff0c;我们可以扫描互…

Excel中一次查询返回多列

使用Excel或wps的时候&#xff0c;有时候需要一次查询返回多列内容&#xff0c;这种情况可以选择多次vlookup或者多次xlookup&#xff0c;但是这种做法费时费力不说&#xff0c;效率还有些低下&#xff0c;特别是要查询的列数过多时。我放了3种查询方法&#xff0c;效果图&…

NodeMCU驱动28BYJ-48型步进电机(Arduino)

NodeMCU NodeMCU开发板 此NodeMCU是在乐鑫公司&#xff08;Espressif Systems&#xff09;生产的ESP-12F模组的基础上封装好的具备WiFi功能的开源IoT开发板。本次选用安信可公司&#xff08;Ai-Thinker&#xff09;生产的CP2102版本的开发板。 28BYJ-48型步进电机 28BYJ-48型…

qt QZipReader详解

1、概述 QZipReader 是 Qt 中用于从 .zip 文件中读取和提取文件内容的类。它提供了便捷的方法来访问压缩包中的文件和目录&#xff0c;并允许你解压缩单个或多个文件。通过 QZipReader&#xff0c;你可以以编程方式读取 .zip 文件中的内容&#xff0c;并提取它们到目标目录中。…

html + css 淘宝网实战

之前有小伙伴说&#xff0c;淘宝那么牛逼你会写代码&#xff0c;能帮我做一个一样的淘宝网站吗&#xff0c;好呀&#xff0c;看我接下来如何给你做一个淘宝首页。hahh,开个玩笑。。。学习而已。 在进行html css编写之前 先了解下网页的组成和网页元素的尺寸吧 1.网页的组成 …

Ollama+OpenWebUI+llama3本地部署

引言 llama3在4月19日刚刚发布&#xff0c;官方的对比结果中在开源模型中堪称世界第一&#xff0c;整好周六日有时间&#xff0c;在魔搭社区上测试一下 2 安装Ollama 2.1 下载Ollama 登录Ollama官网下载Ollama安装包 GitHub&#xff1a;https://github.com/ollama/ollama?t…

【vue2父组件调用子组件方法之slot的使用】

父组件调用子组件方法之slot的使用 具体功能需求&#xff1a; 一个页面&#xff0c;点击按钮&#xff0c;打开一个弹窗。弹窗有自定义表单和公共表单&#xff0c;提交的时候要获取两个表单的数据以及复显表单数据 为什么使用插槽了&#xff0c;因为我需要在弹窗中复用公共表单…

【HarmonyOS】鸿蒙将资源文件夹Resource-RawFile下的文件存放到沙箱目录下

【HarmonyOS】鸿蒙将资源文件夹Resource-RawFile下的文件存放到沙箱目录下 一、问题背景 应用开发中&#xff0c;我们经常会遇到一些文件原先是放在资源文件夹 rawfile下&#xff0c;但是逻辑处理时&#xff0c;需要转移到本地沙箱才能操作。这种情况下&#xff0c;就需要将将…

ubuntu 网络管理--NetworkManager

ubuntu 网络管理--NetworkManager 1 介绍2 NetworkManager 命令2 nmcli 命令显示可用的wifi AP连接wifi检查网络连接 ?? 如何删除删除网络连接查看设备状态添加一个新的以太网连接设置静态 IP 地址启用并测试连接添加新的wifi连接 3 其他命令参考 1 介绍 NetworkManager 是标…

Unity功能模块一对话系统(1)前置准备

也许你也曾被游戏中的对话系统深深吸引&#xff0c;那些精心设计的对白、鲜活的角色配音、甚至是简单的文字对话&#xff0c;往往能让玩家产生强烈的代入感和情感共鸣。如果你正在开发一款游戏&#xff0c;或者计划为你的项目加入一个引人入胜的对话系统&#xff0c;那么 Unity…

【自留】Unity VR入门

帮老师写的&#xff0c;自留&#xff0c;不保证是很好的教程。 1.PICO开发指南&#xff08;官方&#xff09; 在该页面&#xff0c;能找到大部分能实现的功能&#xff0c;以及实现方式。非常推荐&#xff01;PICO Unity Integration SDK | PICO 开发者平台 2.如何快速入门&…

以太网帧结构

以太网帧结构 目前&#xff0c;我们局域网当中应用最广的技术或者协议啊&#xff0c;就是以太网。我们首先来看一下以太网的真结构。这块内容这里边再系统的来给大家去展开说一下&#xff0c;以太网真格式就如下面这个图。所示前面有八个字节&#xff0c;是用于时钟同步的&…

【C语言练习(17)—输出杨辉三角形】

C语言练习&#xff08;17&#xff09; 文章目录 C语言练习&#xff08;17&#xff09;前言题目题目解析整体代码 前言 杨辉三角形的输出可以分三步&#xff0c;第一步构建一个三角形、第二步根据规律将三角形内容填写、第三步将三角形以等腰的形式输出 题目 请输出一个十行的…

Java圣诞树

目录 写在前面 技术需求 程序设计 代码分析 一、代码结构与主要功能概述 二、代码功能分解与分析 1. 类与常量定义 2. 绘制树的主逻辑 3. 彩色球的绘制 4. 动态效果的实现 5. 窗口初始化 三、关键特性与优点 四、总结 写在后面 写在前面 Java语言绘制精美圣诞树…

YashanDB 23.2 YAC -单库多实例架构多活共享集群安装部署指南

一、概述 1.1 文档目标 ​ 本说明旨在指导技术人员在 CentOS 7 x86_64 操作系统上完成崖山数据库企业版 23.2 的共享集群安装与部署。通过系统架构、集群拓扑及部署需求的精确描述&#xff0c;帮助读者在开始安装前对崖山数据库的架构形成清晰认识。本文以高效、稳定、安全为…

uniapp 文本转语音

uniapp 文本转语音 基于 Minimax API 的 UniApp 文本转语音工具&#xff0c;支持文本分段、队列播放、暂停恢复等功能。目前只内置了 Minimax文本转语音Minimax 的语音生成技术以其自然、情感丰富和实时性强而著称 API_KEY、GroupId 获取方法 https://platform.minimaxi.com…

ArrayList源码解析

1、介绍 1、可扩容&#xff0c;允许存储任何元素&#xff0c;包括null。这个类提供了一些方法来操纵数组大小&#xff0c;大致相当于Vector类。 2、ArrayList的容量是表示存储数组元素的大小&#xff0c;容量至少大于列表大小&#xff0c;在容量不足时&#xff0c;会自动扩容至…

ElementPlus 自定义封装 el-date-picker 的快捷功能

文章目录 需求分析 需求 分析 我们看到官网上给出的案例如下&#xff0c;但是不太满足我们用户想要的快捷功能&#xff0c;因为不太多&#xff0c;因此需要我们自己封装一些&#xff0c;方法如下 外部自定义该组件的快捷内容 export const getPickerOptions () > {cons…