本文共 14024 字,大约阅读时间需要 46 分钟。
在UI自动化测试中测试数据一般都是和代码相互分离的,这是为了之后更好地维护自动化的代码。当测试数据发生变化时,只需要在存放数据的模块中进行修改即可,而不需要进入代码中去寻找有数据的部分,从而减轻工作量。在测试中数据驱动有下面几种方式,具体实现和用法如下。
在ddt模块中,@data表示的是类型为元组的数据,@unpack是把元祖,列表,字典拆开传给测试案例。通常情况下,data中的数据按照一个参数传递给测试用例,如果data中含有: 元组,列表,字典等数据,默认是pack(包裹一起的),即一个列表作为一个变量传给函数里的变量。如果把列表的数据进行分解,必须加上@unpack。
例如
@data([a,b],[c,d]) 无@unpack,那么[a,b]一个列表,作为一个参数传给测试用例 有@unpack,那么[a,b]被分解开,作为两个参数a,b 分别传给测试用例import unittestfrom ddt import ddt,data,unpack @ddtclass MyTesting(unittest.TestCase): def setUp(self): pass def tearDown(self): pass @data([1,2,3]) def test_1(self,value): print('test_1 value is ',value) ''' 打印的值是[1,2,3] ''' @data([1,2,3]) @unpack def test_2(self,a,b,c): print('unpack test ',a,b,c) ''' 打印值为1,2,3 ''' if __name__ == '__main__': unittest.main(verbosity=2)
实际情况使用如下
import unittestfrom selenium import webdriverfrom ddt import data, unpack, ddtfrom time import sleep"""ddt来进行数据驱动把@ddt的数据放在一个列表中取用"""def getData(): """数据分离出来放在列表中""" return [ ['123', '', '请输入密码'], ['', '123', '请输入登录名'] ]@ddtclass SinaLogin(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome() self.driver.maximize_window() self.driver.get("https://www.sina.com.cn/") self.driver.implicitly_wait(2) def tearDown(self) -> None: self.driver.quit() # print("end") # 在此处设置data的数据来源,使用getData()返回数据,前面加*是将数据类型转换为元组,因为@data只能接收元组数据 @data(*getData()) @unpack def test_login(self, username, pwd, res): """登录的各种情况""" sleep(2) self.driver.find_element_by_xpath('//*[@id="SI_Top_Login"]/a').click() self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[2]/input').send_keys(username) self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[3]/input').send_keys(pwd) self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[6]/span/a').click() sleep(1) # 获取点击登录后的结果 r1 = self.driver.find_element_by_class_name('login_error_tips').text print('输出结果为:', r1) sleep(5) self.assertEqual(r1, res)if __name__ == '__main__': unittest.main(verbosity=2)
注意:当使用了ddt后,ddt中若有多组数据,那么test函数就会运行多次,即使在代码中test函数只写了一遍。例如在上述例子中,ddt中有2组数据,在运行代码时,结果执行了2次test_login()函数
sina.txt
123请输入密码123请输入登录名
txtTest.py
import unittestfrom time import sleepfrom selenium import webdriver"""从txt文件中读取数据"""def getTxtData(index): """ 读取txt文件 :param index: txt文件的行号,从0开始为第一行 :return: 列表,每一行的内容为列表中的一个元素 """ with open('sina1.txt', 'r', encoding='utf-8') as f: # 按行读取内容 d = f.readlines() return d[index]class SinaLogin(unittest.TestCase): def setUp(self) -> None: self.driver = webdriver.Chrome() self.driver.get('https://www.sina.com.cn/') def tearDown(self) -> None: self.driver.quit() def login(self, uname, pwd): self.driver.find_element_by_xpath('//*[@id="SI_Top_Login"]/a').click() self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[2]/input').send_keys(uname) self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[3]/input').send_keys(pwd) self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[6]/span/a').click() sleep(1) def getInf(self): # 获取点击登录后的结果 r1 = self.driver.find_element_by_class_name('login_error_tips').text # print('输出结果为:', r1) return r1 def test1(self): """密码为空""" self.login(getTxtData(0), getTxtData(1)) # 由于按行读取数据时,每条数据后面会有换行符\n,使用replace()函数去掉函数 # replace(a,b):用b替换字符串中的a self.assertEqual(self.getInf(), getTxtData(2).replace('\n', '')) def test2(self): """用户名为空""" self.login(getTxtData(3), getTxtData(4)) self.assertEqual(self.getInf(), getTxtData(5).replace('\n', ''))if __name__ == '__main__': unittest.main(verbosity=1) """这里的verbosity是一个选项,表示测试结果的信息复杂度,有三个值0 (静默模式): 你只能获得总的测试用例数和总的结果 比如 总共100个 失败20 成功801 (默认模式): 非常类似静默模式 只是在每个成功的用例前面有个“.” 每个失败的用例前面有个 “F”2 (详细模式):测试结果会显示每个测试用例的所有相关的信息"""
csvtest.csv
import csvimport unittestfrom selenium import webdriverimport timedef readCsv(row, col): """ 读取csv文件 :param row: :param col: :return: 嵌套列表 """ rows = [] with open('csvtest.csv', 'r') as f: reader = csv.reader(f) # next函数用于往下读,直到None结束 next(reader, None) for i in reader: rows.append(i) # print(type(rows)) # print(rows) # .join(rows[row][col])是把rows转换成字符串 return ''.join(rows[row][col])class SinaLogin(unittest.TestCase): def setUp(self) -> None: self.driver = webdriver.Chrome() self.driver.get('https://www.sina.com.cn/') def tearDown(self) -> None: self.driver.quit() def login(self, uname, pwd): self.driver.find_element_by_xpath('//*[@id="SI_Top_Login"]/a').click() self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[2]/input').send_keys(uname) self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[3]/input').send_keys(pwd) self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[6]/span/a').click() time.sleep(1) def getInf(self): # 获取点击登录后的结果 r1 = self.driver.find_element_by_class_name('login_error_tips').text return r1 def test1_login(self): self.login(readCsv(0, 0), readCsv(0, 1)) self.assertEqual(self.getInf(), readCsv(0, 2)) def test2_login(self): self.login(readCsv(1, 0), readCsv(1, 1)) self.assertEqual(self.getInf(), readCsv(1, 2))if __name__ == '__main__': unittest.main()
excel文件的读取其实就按照行列,用一个双重循环读取数据保存到一个列表,这个列表实际上是一个嵌套列表,每一行都是一个列表,然后所有的行都被嵌套在一个列表中。例如下面这个ecletest.xlsx读取出来的数据是 [ [ ‘123’, ‘’, ‘请输入密码’],[ ‘’, ‘123’, ‘请输入登录名’ ] ]
eceltest.xlsx
import xlrdfrom selenium import webdriverimport timeimport unittestdef readExcel(row): """ :param row:表示行 :return:文件中行号为row的那一行的值,类型为list """ # 打开文件对象 book = xlrd.open_workbook('eceltest.xlsx', 'r') # 通过sheet选择工作簿 table = book.sheet_by_index(0) return table.row_values(row) # 返回的是文件中行号为row的那一行的值,类型为listdef readExcels(row, col): """ 读取xlsx文件,每一行为一个列表 :return:列表 """ rows = [] book = xlrd.open_workbook('eceltest.xlsx', 'r') table = book.sheet_by_index(0) # 从行号为1处开始读取 for t in range(1, table.nrows): tem = [] # 从列号为0处开始读取 for c in range(0, table.ncols): a = table.row_values(t)[c] # 此处判断数据类型是否为float,如果为float则读取结果会显示为123.0,此处为了去掉123.0后面的.0 if isinstance(a, float): a = '%.0f' % a tem.append(a) rows.append(tem) # rows是一个嵌套列表 return rows[row][col]"""如果不需要像上面readExcels(row, col)函数一样在其中判断类型的话简单一点def readE(): rows = [] book = xlrd.open_workbook('eceltest.xlsx', 'r') sheet = book.sheet_by_index(0) # 从第1行开始逐行读取数据 for row in range(1, sheet.nrows): # 从第0列将数据读入 rows.append(sheet.row_values(row, 0, sheet.ncols)) return rows# 这里返回的是一个嵌套列表,每一行的值为1个列表,所有的行被嵌套在一个rows列表中"""class SinaTest(unittest.TestCase): def setUp(self) -> None: self.driver = webdriver.Chrome() self.driver.get('https://www.sina.com.cn/') def tearDown(self) -> None: self.driver.quit() def login(self, uname, pwd): self.driver.find_element_by_xpath('//*[@id="SI_Top_Login"]/a').click() self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[2]/input').send_keys(uname) self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[3]/input').send_keys(pwd) self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[6]/span/a').click() time.sleep(1) def getMessage(self): # 获取点击登录后的结果 r1 = self.driver.find_element_by_class_name('login_error_tips').text return r1 def test1_login(self): self.login(str(readExcels(0, 0)), str(readExcels(0, 1))) self.assertEqual(str(readExcels(0, 2)), self.getMessage()) # print(str(readExcels(0, 0))) # print(str(readExcels(0, 1))) # print(str(readExcels(0, 2))) def test2_login(self): self.login(str(readExcels(1, 0)), str(readExcels(1, 1))) self.assertEqual(str(readExcels(1, 2)), self.getMessage()) # print(str(readExcels(1, 0))) # print(str(readExcels(1, 1))) # print(str(readExcels(1, 2)))if __name__ == '__main__': unittest.main(verbosity=2)
读取xml文件的时候可以获取标签对之间的数据和子节点中的数据,话不多说,直接上代码
xmltest.xml
https://www.sina.com.cn/
其中新浪首页的网址https://www.sina.com.cn/就是标签对中的数据,而“请输入登录名”就是字节点中的数据,读取方式如下
xmlTest.py
import xml.dom.minidomimport unittestfrom selenium import webdriverimport timedef getXmlData(value): """ 获取单节点标签对之间的数据 :param value:xml文件中单节点的名称 :return:标签对之间的数据 """ # 打开xml文件 dom = xml.dom.minidom.parse('xmltest.xml') # 得到元素对象 db = dom.documentElement # 获取节点标签对象 name = db.getElementsByTagName(value) nameValue = name[0] # 显示标签对之间的数据 return nameValue.firstChild.datadef getXmlUser(parent, child): """ 获取子节点中的数据 :param parent:父节点 :param child:子节点 :return: """ # 打开xml文件 dom = xml.dom.minidom.parse('xmltest.xml') # 得到元素对象 db = dom.documentElement # 获取父标签 itlist = db.getElementsByTagName(parent) # 获取子标签 it = itlist[0] # 获取子标签中的数据 return it.getAttribute(child)class SinaLogin(unittest.TestCase): def setUp(self) -> None: self.driver = webdriver.Chrome() self.driver.get(getXmlData('url')) def tearDown(self) -> None: self.driver.quit() def login(self, uname, pwd): self.driver.find_element_by_xpath('//*[@id="SI_Top_Login"]/a').click() self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[2]/input').send_keys(uname) self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[3]/input').send_keys(pwd) self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[6]/span/a').click() time.sleep(1) def getMessage(self): # 获取点击登录后的结果 r1 = self.driver.find_element_by_class_name('login_error_tips').text return r1 def test1_login(self): self.login(getXmlUser('t1', 'u1'), getXmlUser('t1', 'p1')) self.assertEqual(getXmlUser('erroMsg', 'unameNull'), self.getMessage()) def test2_login(self): self.login(getXmlUser('t2', 'u2'), getXmlUser('t2', 'p2')) self.assertEqual(getXmlUser('erroMsg', 'pwdNull'), self.getMessage())if __name__ == '__main__': # print(getXmlData('url')) # print(getXmlUser('erroMsg', 'unameNull')) unittest.main(verbosity=2)
在对数据库进行操作的时候和Java一样,先要建立连接,然后获取游标,然后对游标进行操作
mySQLtest.py
import pymysqlimport unittestfrom selenium import webdriverimport timedef connectSQL(): try: con = pymysql.connect(host='127.0.0.1', user='root', passwd='123456', db='auto') return con except: raise Exception('连接数据库失败')def insertOne(s1, s2, s3): """ 插入一条数据 :return: """ # 建立连接,返回一个连接对象 con = connectSQL() # 得到一个可以执行SQL语句的光标对象 cur = con.cursor() # 定义sql语句 sql = 'INSERT INTO t VALUES(%s, %s, %s)' # 定义插入的值 params = (s1, s2, s3) # 执行语句 cur.execute(sql, params) # 提交 con.commit() # 关闭连接 cur.close() con.close() print('完成一条数据插入')def insertMany(): """ 插入多条数据 :return: """ con = connectSQL() cur = con.cursor() sql = 'INSERT INTO t VALUES(%s, %s, %s)' params = [('t2', 't2', 't2'), ('t3', 't3', 't3') ] cur.executemany(sql, params) con.commit() cur.close() con.close() print('多条插入完成')def getOne(): """ 查询一条数据 :return: cur.fetchone()元组 """ con = connectSQL() cur = con.cursor() sql = 'SELECT *FROM t WHERE uname=%s' param = ('123',) cur.execute(sql, param) # print('查询的结果:', cur.fetchone()) return cur.fetchone() # cur.close() # con.close()def getMany(): """ 查询多条数据 :return:cur.fetchall()嵌套元组 """ con = connectSQL() cur = con.cursor() sql = 'SELECT *FROM t' # print("查询的结果:") cur.execute(sql) # print(cur.fetchall()) # for item in cur.fetchall(): # print(item) return cur.fetchall() # cur.close() # con.close()class SinaLogin(unittest.TestCase): def setUp(self) -> None: self.driver = webdriver.Chrome() self.driver.get('https://www.sina.com.cn/') def tearDown(self) -> None: self.driver.quit() def login(self, uname, pwd): self.driver.find_element_by_xpath('//*[@id="SI_Top_Login"]/a').click() self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[2]/input').send_keys(uname) self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[3]/input').send_keys(pwd) self.driver.find_element_by_xpath('//*[@id="SI_Top_LoginLayer"]/div/div[2]/ul/li[6]/span/a').click() time.sleep(1) def getMessage(self): # 获取点击登录后的结果 r1 = self.driver.find_element_by_class_name('login_error_tips').text return r1 def test1_login(self): self.login(getMany()[0][0], getMany()[0][1]) self.assertEqual(self.getMessage(), getMany()[0][2]) def test2_login(self): self.login(getMany()[1][0], getMany()[1][1]) self.assertEqual(self.getMessage(), getMany()[1][2])if __name__ == '__main__': # insertMany() # getOne() # getMany() # 因为在数据库中含有值为null的数据,所以取出来之后不会自动变为空值,所以会产生错误,但是本代码读取数据本身没有问题 unittest.main(verbosity=2) # print(getMany()[0][0]) # print(getMany()[0][1]) # print(getMany()[0][2])
总结:其实UI自动化的数据驱动本质上就是从各种各样的数据存储中读取数据,然后再实行自动化测试的调用这些读取数据的函数提供测试用例的数据,如果你已经掌握了unittest测试套件的用法,在这部分你只需要掌握python中对各种文件的读取方法即可。
转载地址:http://ecrxz.baihongyu.com/