框架目录:
common:存放编公共方法类,获取CSV文件、获取配置文件数据、获取日志文件对象、读取数据工具
config:存放一些配置文件、比如日志配置文件、数据文件存放路径
data:存放测试数据
log:存放系统运行日志
pages:存放基于页面操作的基类base_page.py和对页面属性和操作的类如login_page.py
report:存放测试报告
screenshots:存放测试截图
test_case:存放测试脚本
test_run:自动化执行测试脚本,生成测试报告
前面介绍了UI自动化进销存系统之用户登录(一),UI自动化进销存系统之会员管理(二)
怎么对页面通用元素封装,封装了base_page.py,还有对页面登录login_page.py
和会员管理的添加操作member_manage_page.py。写了测试脚本验证用户登录和添加会员的操作,接下来按照测试框架介绍对于日志的封装,测试数据读取,运行测试脚本生成测试截图和测试报告
我的理解测试框架是为了更好方便管理维护自动化测试的方式,以进销存系统的web自动化为例,首先我们可以先从page目录写起,写一个basePage类,封装一个通用的页面操作,比如通用的元素定位方法,下拉框的定位方法等。然后再page目录下,针对业务操作,一个页面对应一个Page类,比如登录LoginPage,需要继承basePage。登录的LoginPage就是存放页面操作的类。
然后我们可以定义一个util目录,存放一些测试工具,定义一个myunit类,定义页面打开,初始化driver和浏览器关闭的方法。在testcases目录下定义一个测试脚本,比如测试登录,的login_test.py,继承myunit,在把LoginPage导入,在里面输入测试数据开始测试。
开始测试肯定会遇到问题,所以我们集成了logging,在每步操作有一个操作日志,方便我们定位问题。
在做测试时,我们想测试多组测试数据,如果每组测试数据写一个方法,重复代码冗余且不方便数据管理,所以我们需要在data目录下存放测试数据,以便来做数据驱动,在测试验证时,我们也会需要一些测试截图。
读logging配置文件,读测试数据,读取截图文件、读取脚本存放目录等我们都需要涉及到路径,如果我们在每次使用的时候都直接写路径,这些路径就很分散不方便管理,所以我们可以使用一个config.yaml存放测试需要的路径、测试url,测试数据库信息等
所有的准备都做完了,我们就把所有的测试脚本给组织起来,在test_run目录下,创建一个run类,做自动化测试,并把生成的测试报告存放在report目录下
1、python中读取yaml文件内容
为了方便一些配置文件、项目url还有数据驱动文件的变动对项目造成影响,在配置文件中写入存放地址,然后再定义一个工具类获取配置文件内容,通过关键字来读取对应的值或者路径
config/conf.yaml
loggerConfigPath: ../config/logger.conf #日志配置文件路径
screenshotpath: ../screenshots/ #截图存放路径
login_url: http://192.168.47.15:8080/woniusales/ #项目url
login_data: ../data/login.csv #登录数据csv文件
login_json: ../data/logindata.json #登录数据json文件路径
case_dir: ../test_cases #测试脚本存放路径
report_dir: ../report #测试报告存放路径
在Util类中定义了一个类方法读取配置文件内容,使用时导入Util类,直接调用Util.get_jsondata(Util.get_conf()["关键字"]),获取对应的值
common/util.py
import yaml
class Util:
# 以字典格式返回配置文件的内容
@classmethod
def get_conf(cls):
with open("../config/conf.yaml",'r',encoding='UTF7') as f:
data = yaml.load(f,Loader=yaml.FullLoader)
return data
if __name__ == "__main__":
data = Util.get_jsondata(Util.get_conf()["login_json"]) #获取yaml文件中login_json对应的值
print(data)
2、python集成logging日志
项目运行一旦出现问题日志信息就非常重要,日志是定位问题的重要手段,在输出日志内容时,对日志内容进行格式化设置,能方便查看问题位置。另外一个项目中会有很多的日志采集点,而日志的采集点必须结合业务属性来设置。比如在登录代码执行前可以插入“准备登录”日志信息
日志配置文件
在config目录下新建一个logger.conf,将下面内容复制进去,修改args=('../log/psi.log', 'a')里面日志存放路径为你要保存日志的路径
config/logger.conf
[loggers]
keys=root,file,fileAndConsole
[handlers]
keys=fileHandler,consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_file]
level=DEBUG
handlers=fileHandler
qualname=file
propagate=1
[logger_fileAndConsole]
level=DEBUG
handlers=fileHandler,consoleHandler
qualname=fileAndConsole
propagate=0
[handler_consoleHandler]
class=StreamHandler
args=(sys.stdout,)
level=DEBUG
formatter=simpleFormatter
[handler_fileHandler]
class=FileHandler
args=('../log/psi.log', 'a')
level=DEBUG
formatter=simpleFormatter
[formatter_simpleFormatter]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
datefmt=%Y-%m-%d %H:%M:%S
日志工具类的封装:
common/util.py
import logging.config
filepath = Util.get_conf()["loggerConfigPath"] #通过工具类获取logger配置文件路径
logging.config.fileConfig(filepath)
logger = logging.getLogger('fileAndConsole')
日志文件的使用:
在登录page/login_page.py模块中导入logger,为了让方便问题定位,可以详细的记录每一步的操作,从用户开始登录、到用户输入账号、密码、验证码、点击登录按钮登录
import time
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from common.util import logger,Util
from pages.base_page import BasePage
class LoginPage(BasePage): #定义登录页面的属性和方法
username = (By.ID,"username")
password = (By.ID,"password")
verifycode = (By.ID,"verifycode")
lgoin_button = (By.XPATH,"//button[contains(@onclick,'doLogin')]")
flag = (By.XPATH,'//a[contains(text(),"admin")]')
def user_login(self,us,pw,ve):
logger.info("=================开始登录================")
logger.info("输入用户名{}".format(us))
self.baseFindElement(self.username).send_keys(us)
logger.info("输入密码{}".format(pw))
self.baseFindElement(self.password).send_keys(pw)
logger.info("输入验证码{}".format(ve))
self.baseFindElement(self.verifycode).send_keys(ve)
logger.info("==============点击登录按钮================")
self.baseFindElement(self.lgoin_button).click()
logger.info("================登录成功==================")
3、给项目添加截图
在工具类common/util.py添加一个截图方法。
#添加截图
@classmethod
def get_screenshot(cls,driver,path):
driver.get_screenshot_as_file(path)
在做页面操作时调用
Util.get_screenshot(self.driver,Util.get_conf()["screenshotpath"]+now+".png")
4、执行自动化测试用例
在所有的测试脚本都写完了,当然就需要有个入口,能全部执行这些测试脚本,
在conf.yaml配置文件中定义测试脚本和测试报告的位置,unittest.TestLoader().discover(文件路径,匹配文件格式)查找测试脚本,运行测试脚本,使用HTMLTestRunner生成测试报告。
test_run/run.py
import unittest,time
from HTMLTestRunner_cn import HTMLTestRunner
from common.util import Util
test_dir = Util.get_conf()["case_dir"] #读取测试脚本的目录
report_dir = Util.get_conf()["report_dir"] #读取测试报告目录
dis = unittest.TestLoader().discover(test_dir,"*_test.py") #查找测试脚本下以_test结尾python文件
now = time.strftime("%Y-%m-%d %H %M %S")
report_name = report_dir + '/'+now+'test_report.html'
runner = HTMLTestRunner(
title="进销存系统的自动化测试报告",
description="v1.0测试结果",
stream=open(report_name,'wb'),
verbosity=2
)
runner.run(dis)
5、使用csv文件存放测试数据
data/login.csv
admin,admin123,0000 admin,,0000 admin,admin123, Util工具类中读取login.CSV文件
#读取数据
@classmethod
def get_testdata_by_row(cls,csv_file,line): #csv_file文件名称 line行数
logger.info("开始读取数据")
with open(csv_file,'r',encoding="utf-8") as f:
reader = csv.reader(f)
for index,row in enumerate(reader,1):
if index == line:
return row #返回该行数据
在测试脚本中使用读取的测试数据,test_cases/login_test.py
def test_login_succcess(self):
lp = LoginPage(self.driver)
data = Util.get_testdata_by_row(Util.get_conf()["login_data"],1)
lp.user_login(data[0],data[1],data[2])
self.assertTrue(lp.check_login())
def test_login_failed_without_username(self):
lp = LoginPage(self.driver)
data = Util.get_testdata_by_row(Util.get_conf()["login_data"], 2)
lp.user_login(data[0],data[1],data[2])
self.assertFalse(lp.check_login())
6、数据驱动
这一节主要介绍使用parameterized做数据驱动,以登录页面为例,在data目录下创建一个logindata.json文件,存放四组数据,用户名、密码、验证码、是否登录成功。
data/logindata.json
[
{
"username": "admin",
"password": "admin123",
"verify": "0000",
"flag": true
},
{
"username": "",
"password": "admin123",
"verify": "0000",
"flag": false
},
{
"username": "admin",
"password": "",
"verify": "0000",
"flag": false
},
{
"username": "admin123",
"password": "admin123",
"verify": "0000",
"flag": false
}
]
在Util工具类中读取JSON文件,因为@parameterized.expand(data)中存放的数据是[(),(),()]列表嵌套元组,所以这里在把值取出来之后,以元组的形式存在列表list_data中
@classmethod
def get_jsondata(cls,jsonname):
with open(jsonname,"r") as f:
datas = json.load(f)
list_data = []
for data in datas:
username = data["username"]
password = data["password"]
verify = data["verify"]
flag = data["flag"]
list_data.append((username,password,verify,flag))
return list_data
在test_cases/login_test.py
这里就是在要做数据驱动的方法前面加上@parameterized.expand(data),该方法上面的参数和data里面每次读取的值一一对应,当flag为True时,说明这组测试数据是正向的测试数据,就断言返回的lp.check_login()返回值是否为True,否则就断言返回的值lp.check_login()是否为False
from common.util import Util
from pages.login_page import LoginPage
from common.myunit import MyUnit
from parameterized import parameterized
import unittest
class LoginTest(MyUnit):
data = Util.get_jsondata(Util.get_conf()["login_json"])
@parameterized.expand(data)
def test_login_param(self,un,pw,ver,flag):
lp = LoginPage(self.driver)
lp.user_login(un,pw, ver)
if flag:
self.assertTrue(lp.check_login())
else:
self.assertFalse(lp.check_login())