数据驱动测试模式
基本概念
数据驱动(Data-Driven)是一种软件测试和开发方法,强调通过数据来驱动测试用例的执行和结果验证。与传统的硬编码测试用例不同,数据驱动测试允许使用外部数据源(如 CSV 文件、Excel 表格、数据库等)来提供测试输入和期望输出,从而提高测试的灵活性和可维护性。测试脚本只是作为一个驱动或者类似于一个传送数据的机制。测试数据和测试行为进行完全分离这样的测试脚本设计模式叫做数据驱动。
数据驱动的优势
提高可维护性:测试用例与测试数据分离,修改数据不需要更改测试逻辑。
复用性:同一测试逻辑可以使用不同的数据集进行多次测试。
覆盖更多场景:可以轻松地添加新的测试数据,覆盖更多的测试场景。
减少重复代码:通过参数化测试,减少了重复的测试代码。
数据驱动的一般流程
1)测试架构搭建
确定测试目标,明确需要测试的功能或模块,确定测试的目标和范围。
2)设计测试用例:
根据需求文档和功能说明,设计测试用例,确定输入数据和预期结果。
3)选择数据源:
确定用于存储测试数据的数据源,可以是 CSV 文件、Excel 表格、数据库、JSON 文件等。
4)编写测试脚本:
编写测试脚本,使用数据驱动框架(如 pytest、JUnit、TestNG 等)来读取数据源中的测试数据,并将其传递给测试用例。
5)测试、分析、维护:
运行测试脚本,测试框架会根据数据源中的数据多次执行测试用例。收集测试执行结果,分析测试通过和失败的情况,生成测试报告。根据需要更新和维护测试数据,确保测试覆盖最新的需求变化
python DDT
python的使用ddt模块进行数据驱动测试,DDT通过使用装饰器来实现,主要包括unittest框架中的装饰器如@ddt、@data、@unpack和@file_data。
pip install ddt 使用装饰器:@ddt:用于装饰测试类,表示该类使用DDT。
@data:用于装饰测试用例,传递参数。可以是列表、元组、字典等。
@unpack:用于拆分元组或列表中的元素,使其作为独立的参数传递给测试用例。
@file_data:从JSON或YAML文件中加载数据,支持yaml和json格式。
实例
python">import unittest
import time
from ddt import ddt,data,unpack
@ddt
class Mytest(unittest.TestCase):def setUp(self):print('test begin----')time.sleep(1)def tearDown(self):print('test end------')time.sleep(1)@data('hello','word')def test_print(self,value):print('传入值是: '+value)@data([1,2,3],[1,2,2])@unpack #会将d([a,b])拆分两个参数def test_add(self,a,b,expected):actual=int(a)+int(b) #测试参数[1,2,3] 1+2=3 和1+2=2?expected=int(expected)self.assertEqual(actual,expected)
if __name__=='__main__':unittest.main()
官方实例
https://ddt.readthedocs.io/en/latest/example.html
复杂案例测试
一般的测试流程 1)打开浏览器; 2)输入账号、密码;3)单机登录按钮,进行断言; 4)退出系统关闭浏览器 数据驱动模型中测试: 1)设计架构 -》如data存放excel文件;2)设计用例如用例号、测试标题、预期结果;3) 对excel文件数据进行操作;4)使用ddt生成自动化测试用例;5)运行脚本对成功或失败的用例进行标记
1,被测试界面准备
tddt.html文件
<!DOCTYPE html>
<html lang='zh-CN'><head><meta charset="utf-8"><meta content="IE=edge"><title>后台管理</title><link href='./style.css' rel='stylesheet' type='text/css' /></head><body><h1>后台管理系统</h1><div class='login box'><!---form starts---><form action='#'><div class='txt'><input placeholder='请输入您的邮箱' id='ty-email'/><div class='msg' id='ty-email-error'></div></div><div class='txt'><input type='password' placeholder='请输入密码' id='ty-pwd'/><div class='msg' id='ty-pwd-error'></div></div><div class='login-btn'><input type='submit' onclick='login()' value='登录'></div><div class='account'><p>账号:admin@test.com; 密码: admin</p></div></form></div><!--- form ends---><!--copyright--><div class='copyright'><p>Copyright 2024 </p></div><!--copyright--></body>
</html>
2、框架搭建
测试框架的搭建是确保软件质量的关键步骤,主要包括选择合适的测试框架、规划测试用例、实现自动化测试等方面,测试结构通常包含的文件目录结构如下:
conf目录 | 用于存放项目运行环境和执行环境相关的配置参数 |
logic | 用于封装与项目相关的关键字和业务逻辑 |
testsuite | 用于编写测试用例,pytest默认约定以test开头的文件和方法为测试用例,不满足条件的不会被执行,建议按照特性建立文件夹对测试用例进行分类 |
utils | 用于存放与业务无关的实用程序,如辅助方法 |
common | 用于存储公共代码,如路径处理等 |
utils | 用于存储工具类,主要处理各种文件的工具函数 |
data | 用于存储测试过程中使用到的数据 |
report | 用于存储测试报告 |
config | 用于存储配置文件,如测试地址等 |
README.md | 用于说明自动化工程的文档 |
3、设计用例:
将测试用例放到excel文件里,这里列举了5个当然还有很多测试用例比如密码长度、邮箱格式等等
处理数据脚本
新建文件夹commont并在其内创建ExcelUtil.py 脚本,处理从excel获得的数据,casedata.xlsx放在data文件夹下,casedata.xlsx的数据是在名为Login的Sheet里
python">from openpyxl import load_workbook
import os
class ExcelUtil(object):def __init__(self,excel_path=None,sheet_name=None):'''获取execl 工作表'''if excel_path==None:current_path=os.path.abspath(os.path.dirname(__file__))self.excel_path=current_path+'/../data/casedata.xlsx'else:self.excel_path=excel_pathif sheet_name==None:self.sheet_name='Sheet1'else:self.sheet_name=sheet_nameself.data=load_workbook(self.excel_path)self.sheet=self.data[self.sheet_name]def get_data(self):'''读取文件数据每一行一个list所有数据一个大list'''rows=self.sheet.rowsrow_num=self.sheet.max_rowcol_num=self.sheet.max_columnif row_num<=1:print('总行数小于1,没有数据')else:case_all=[]for row in rows:case=[]for i in range(col_num):case.append(row[i].value)case_all.append(case)return case_all
if __name__=='__main__':sheet='Login'file=ExcelUtil(sheet_name=sheet)print(file.get_data())
4、数据生成测试用例
case文件夹下创建TestLogin.py 脚本, get_case()将得到的数据转为测试case输入。 Login类定义login()方法,简化起见将浏览器driver、邮箱地址、密码作为输入参数,另外断言处理是针对输入测试结果的反馈。使用ddt生成测试用例
python">
from selenium import webdriver #导入webdriver
import ddt,unittest,time,os,sys
from selenium.webdriver.common.by import By
from ddt import ddt,data,unpackcurrent_file_path = os.path.abspath(__file__)
project_dir = os.path.dirname(os.path.dirname(current_file_path))
sys.path.append(project_dir)
import commont.ExcelUtilclass Case(object):def __init__(self):passdef get_case(self):'''获取数据剔除首行标题,并以邮箱地址、密码、预期结果定位、预期结果顺序保存'''sheet='Login'file=commont.ExcelUtil.ExcelUtil(sheet_name=sheet)data=file.get_data()email_index=data[0].index('邮箱地址')password_index=data[0].index('密码')excepted_element_index=data[0].index('预期结果定位')excepted_index=data[0].index('预期结果')data_length=data.__len__()all_case=[]for i in range(1,data_length): #除去标题行case=[]case.append(data[i][email_index])case.append(data[i][password_index])case.append(data[i][excepted_element_index])case.append(data[i][excepted_index])all_case.append(case)return all_case
class Login(object):def __init__(self,driver):self.driver=driverdef login(self,email,password):time.sleep(1)if email!=None:email_elem=self.driver.find_element(By.ID,'ty-email')email_elem.send_keys(email)time.sleep(1)if password!=None:paswword_elem=self.driver.find_element(By.ID,'ty-pwd')paswword_elem.send_keys(password)time.sleep(1)login_btn=self.driver.find_element(By.CSS_SELECTOR,'input[value="登录"]')login_btn.click()def login_assert(self,assert_type,assert_message):time.sleep(1)if assert_type=='email error':email_message=self.driver.find_element(By.ID,'ty-email-error').textassert email_message==assert_messageelif assert_type=='password error':password_message=self.driver.find_element(By.ID,'ty-pwd-error').textassert password_message==assert_messageelif assert_type in ['login success','login fail']:login_message=self.driver.switch_to.alert.textassert login_message==assert_messageelse:print('输入类型断言不正确')
@ddt
class TestLogin(unittest.TestCase):def setUp(self):self.driver=webdriver.Chrome()url='file:///E:/tddt.html'self.driver.implicitly_wait(20)self.driver.maximize_window()self.driver.get(url=url)def tearDown(self):self.driver.quit()case=Case().get_case()@data(*case)@unpackdef test_login(self,email,password,asser_type,assert_message):login=Login(driver=self.driver)login.login(email=email,password=password)login.login_assert(assert_type=asser_type,assert_message=assert_message)
if __name__=='__main__':unittest.main()
5、测试执行与结果分析
创建run_main.py与case文件夹、report、data同级别
python">import os,time,unittest
import HTMLTestRunner
current_path=os.path.abspath(os.path.dirname(__file__))
report_path=current_path+'/report/'
#生成时间戳格式的日志文件
now=time.strftime("%Y-%m-%d_%H-%M",time.localtime(time.time()))
title=u'后台系统'
report_abspath=os.path.join(report_path,title+now+'.html')
def all_case():discover=unittest.defaultTestLoader.discover('./case/',pattern="Test*.py")return discoverif __name__=='__main__':all_case()fp=open(report_abspath,'wb')runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title=title)runner.run(all_case())fp.close()
6、报告及分析
会在report里生成“后台系统时间戳.html” 测试报告
fail的原因是,在界面tddt.html里没有复杂处理<div class='msg' id='ty-pwd-error'></div>,password_message=self.driver.find_element(By.ID,'ty-pwd-error').text这里结果为空,邮箱的fail原因也是一样; 对于error的case失败原因是html文件没有alert处理造成脚本里 login_message=self.driver.switch_to.alert.text 然后出现error selenium.common.exceptions.NoAlertPresentException: Message: no such alert