python自动化web测试 文档包含的内容: 1.web自动化说明与框架 2. web自动化测试例子 3.环境的搭建(附录) 近期,由于对需要对迪备产品进行web测试,上网找了些关于python进行自动化测试的资料,发现进行Web测试,可以通过Webdriver去模拟用户的操作,当前比较出色就是splinter+selenuim,基本上支持IE,fixfox和chrome浏览器的基本操作。 有了第三方模拟浏览器工具的帮助,然后就要想想产品的测试框架了,因为我们的产品基本上进行的是功能测试,而python自己自带的unittest是进行单元测试的,不过,似乎单元测试也是有层次的,小单元到大单元,再到一个个的模块,最后整体的软件测试。其实我们的产品的测试,也可以这样,一个个测试单元组成一个模块,例如登录迪备就是一个小模块,里面是测试单元,则是一个个的测试用例。然后再由这些小模块再组成大模块,最后成为我们产品的测试的框架。python提供了testsuit(测试包) 就是把小模块组合成大模块的类。整体来看还是典型的树形结构,同时每一层都可以有测试的进行。 有点头绪了,就开始尝试用python进行测试了,刚开始的参数是默认的,逐渐变成从txt文件,到xml文件去读取测试用例。 继续需要做的就是把这些测试用例做成模块,构架出框架来。 以下些例子:主要参考 http://splinter.cobrateam.info/docs/ splinter的官网,自动测试的Web框架,包含小例子和API的说明 http://docs.python.org/library/unittest.html python单元测试框架的文档和小例子 http://docs.python.org/library/xml.etree.elementtree.html python对XML的解析 另外还需要了解一下html,css,和javacript 其实这只是例子,真正的还需要很多的细化。 例子1:批量增加,删除用户,增加ftp服务器,删除ftp服务器的参数都从XML文件中读取,并可以从server端的mysql数据库获取数据来assertEqual. 例子2:创建oracle数据库备份实例和mysql数据库备份实例,在备份之前先往数据库里面添加数据,在恢复后,再查询是否同样的数据在数据库中(未完成) xml文件在以后就变成测试用例输入的数据,如边界值,只需要修改xml文件,再运行python脚本来测试 username.xml <?xml version = "1.0" encoding= "UTF-8"?> <addUsers> <user name='hzhida' password = 'dingjia' email ='hzhida95@gmail.com' telephone='13424341233' /> <user name='dingjia' password= 'dingjia' email ='dbackup@gmail.com' telephone= '3143142131' /> <user name = 'Tommy' password = 'djifadf' email ='34131431@qq.com' telephone ='1341314312'/> <user name = 'Janny' password = 'dafjljfd' email ='erfdjlafj@qq.com' telephone = '143143134'/> </addUsers> ftp.xml <?xml version = "1.0" encoding= "UTF-8"?> <TotalFTP> <ftp name='ftpunbuntu' ip='192.168.88.245' port ='21' username = 'hzhida' password = 'dingjia' path ='FTPFile/backup/'/> <ftp name='windowftp' ip='192.168.88.162' port = '21' username = 'hzhida' password = 'dingjia' path='FTPFile\backup'/> </TotalFTP> #-*-coding:utf-8-*- from __future__ import with_statement import os import unittest import time from splinter import Browser from random import randint from xml.etree import ElementTree as ET import MySQLdb class DbackupTestCase(unittest.TestCase): @classmethod def setUpClass(cls): cls.browser = Browser('firefox') cls.connection = MySQLdb.connect(user='scutech',db ='dpsora_1',passwd='dingjia',host='192.168.88.245') cls.cursor = cls.connection.cursor() @classmethod def setDownClass(cls): cls.browser.quit() def login(self, username, password): if self.browser.find_by_id('lhgfrm_lhgdgId'): with self.browser.get_iframe('lhgfrm_lhgdgId') as frame: frame.find_by_id('trialRadio').click() frame.find_by_id('continue').click() self.browser.fill('username',username) self.browser.fill('password',password) self.browser.find_by_name('Submit').click() time.sleep(0.5) assert self.browser.is_element_present_by_tag('body') #@unittest.skip('skip register') def test_register(self): self.browser.visit('http://192.168.88.245/dbackup') self.login(username='admin', password= 'admin') time.sleep(1) #because iframe has no id,so can't get it #iframe = self.browser.find_by_tag('iframe') #iframe.find_link_by_href('../Supermanage/LocalCfgManage.php').click() #self.browser.find_by_id('Navigate1').click() #self.browser.find_link_by_href('Register.php').click() navigate = self.browser.find_by_css('.NavigateMBG') for i in navigate: print i navigate[2].click() navigate[2].find_by_tag('a')[2].click() time.sleep(0.5) file = ET.parse('username.xml') users = file.findall('./user') for user in users: self.browser.find_by_id('username').fill(user.get('name')) self.browser.fill('password',user.get('password')) self.browser.find_by_id('confirmpassword').fill(user.get('password')) self.browser.find_by_id('email').fill(user.get('email')) self.browser.find_by_id('telephone').fill(user.get('telephone')) self.browser.find_by_name('Submit').click() time.sleep(1) alert = self.browser.get_alert() alert.accept() #select from database to check whether the user had registered self.cursor.execute("""select username from v_loginaccountinfo where enabled = '1' and username= %s""" , (user.get('name'),)) self.assertEqual(self.cursor.fetchone(),(user.get('name'),)) #navigate[2].click() #navigate[2].find_by_tag('a')[2].click() self.browser.find_by_id('Navigate1').click() self.browser.find_link_by_href('Register.php').click() @unittest.skip('skip user land') def test_User(self): self.browser.visit('http://192.168.88.245/dbackup') file = ET.parse('username.xml') users = file.findall('./user') for user in users: self.login(username = user.get('name'), password = user.get('password')) time.sleep(1) iframe = self.browser.find_by_tag('iframe') iframe[0].find_by_tag('a')[3].click() self.browser.visit('http://192.168.88.245/dbackup') @unittest.skip('del user') def test_delUser(self): self.browser.visit('http://192.168.88.245/dbackup') self.login(username ='admin', password ='admin') time.sleep(0.5) navigate = self.browser.find_by_css('.NavigateMBG') navigate[2].click() navigate[2].find_by_tag('a')[1].click() table=self.browser.find_by_id('mytable') tr=table.find_by_tag('tr') file = ET.parse('deluser.xml') user = file.find('./user') i=1 while i< len(tr): if tr[i].find_by_tag('td')[1].value == user.get('name'): tr[i].find_by_tag('input').click() self.browser.find_by_id('nDeleteAccountID').click() time.sleep(1) alert = self.browser.get_alert() alert.accept() alert = self.browser.get_alert() alert.accept() time.sleep(1) break else: i+=1 @unittest.skip('skip addFTP') def test_addFTP(self): self.browser.visit('http://192.168.88.245/dbackup') self.login(username = 'admin', password = 'admin') time.sleep(0.5) navigate = self.browser.find_by_css('.NavigateMBG') navigate[0].click() navigate[0].find_by_tag('a')[2].click() file = ET.parse('ftp.xml') ftps = file.findall('./ftp') for ftp in ftps: self.browser.find_by_id('strFTPNameID').fill(ftp.get('name')) ip = [ ftp.get('ip').strip().split('.') for ipnum in ftp.get('ip') ] self.browser.find_by_id('IP1').fill(ip[0][0]) self.browser.find_by_id('IP2').fill(ip[0][1]) self.browser.find_by_id('IP3').fill(ip[0][2]) self.browser.find_by_id('IP4').fill(ip[0][3]) self.browser.find_by_id('strPortID').fill(ftp.get('port')) self.browser.find_by_id('strFTPLoginNameID').fill(ftp.get('username')) self.browser.fill('strFTPLoginPW',ftp.get('password')) self.browser.find_by_id('ConfirmPWID').fill(ftp.get('password')) self.browser.find_by_id('nPathID').fill(ftp.get('path')) self.browser.find_by_id('Submit').click() alert = self.browser.get_alert() alert.accept() time.sleep(1) self.cursor.execute("""select FtpName from ftpserver where Disabled ='0' and FtpName = %s""",(ftp.get('name'),)) self.assertEqual(self.cursor.fetchone(),(ftp.get('name'),)) self.browser.find_by_id('Navigate5').click() self.browser.find_link_by_href('FtpCfg.php').click() unittest.main() 例子2: #-*-coding:utf-8-*- from __future__ import with_statement import os try: import unittest2 as unittest except ImportError: import unittest import time from splinter import Browser from random import randint import cx_Oracle import mySQLdb import cPickle from sys import modules class DbackupTestCase(unittest.TestCase): # support 2.7,but not support 2.6.all the testcases just have one instance @classmethod def setUpClass(cls): cls.browser=Browser('firefox') cls.connection =cx_Oracle.connect('system','dingjia','192.168.88.245/oracle') cls.cursor =cls.connection.cursor() @classmethod def setDownClass(cls): cls.browser.quit() #every testcase has one instance # def setUp(self): # self.browser=Browser('firefox') # def setDown(self): # self.browser.quit() def do_login_if_need(self, username, password): if self.browser.find_by_id('lhgfrm_lhgdgId'): with self.browser.get_iframe('lhgfrm_lhgdgId') as frame: frame.find_by_id('trialRadio').click() frame.find_by_id('continue').click() self.browser.fill('username',username) self.browser.fill('password',password) self.browser.find_by_name('Submit').click() assert self.browser.is_element_present_by_css('.none') def test_create_oracle_event(self): #In order to check the backup process is correct, so before it backup,insert some data in database, and when it recover,so it is a chance to select the same data in database to terify the process of backup create_table=""" create table python_modules( module_name VARCHAR2(50) NOT NULL, file_path VARCHAR2(300) NOT NULL )""" self.cursor.execute(create_table) M= [] for m_name, m_info in modules.items(): try: M.append((m_name,m_info.__file__)) except AttributeError: pass cursor.prepare("insert into python_modules(module_name,file_path) values(:1,:2)") cursor.executemany(None,M) #submit transation self.connection.commit() #open home and login self.browser.visit('http://192.168.88.245/dbackup') self.do_login_if_need(username='hzhida', password ='dingjia') time.sleep(0.5) assert self.browser.is_element_present_by_id('treeNodeId1') self.browser.find_link_by_href('javascript:void(0)')[1].click() time.sleep(1) element=self.browser.find_by_css('.tree_item_child') #self.browser.find_link_by_href('javascript:void(0)')[2].click() element[0].find_by_css('.tree_item_click').first.click() #element[0].find_by_tag('a').first.click() self.browser.find_by_id('Navigate7').click() time.sleep(1) self.browser.find_link_by_href('#wizard-2').click() self.browser.find_link_by_href('#wizard-3').click() self.browser.find_by_id('ftpRadio').click() self.browser.find_link_by_href('#wizard-4').click() number = [randint(1000,9999) for i in range(1)] jobname='job'+'_'+ str(number[0]) self.browser.find_by_id('jobName').fill(jobname) self.browser.find_by_id('middlelevelRadio').click() self.browser.find_link_by_href('#wizard-5').click() self.browser.find_by_css('.submit_btn').click() time.sleep(1) self.browser.select('Filtermenu','JobMonitorManage.php?FType=1') time.sleep(0.1) self.browser.select('Filtermenu','JobMonitorManage.php?FType=0') time.sleep(0.1) self.assertEqual(self.browser.find_link_by_href('javascript:void(0)').first.value,jobname) print self.browser.find_by_id('CompletePercent0').value print self.browser.find_by_id('strBeginTime0').value print self.browser.find_by_id('strUsedTime0').value time.sleep(10) @unittest.skip('skip test mysql') def test_create_mysql_event(self): self.browser.visit('http://192.168.88.245/dbackup') self.do_login_if_need(username='hzhida',password='dingjia') time.sleep(0.5) assert self.browser.is_element_present_by_id('treeNodeId1') self.browser.find_link_by_href('javascript:void(0)')[1].click() time.sleep(1) element=self.browser.find_by_css('.tree_item_child') element[1].find_by_tag('a').first.click() self.browser.find_by_id('Navigate7').click() self.browser.find_by_id('sqlSelectAllCheckbox').click() time.sleep(2) navigate=self.browser.find_by_css('.nav') navigate[0].find_by_css('.next').click() self.browser.find_by_id('ftpRadio').click() navigate[1].find_by_css('.back').click() time.sleep(1) navigate[0].find_by_css('.next').click() navigate[1].find_by_css('.next').click() self.browser.find_by_id('lowlevelRadio').click() number = [randint(1000,9999) for i in range(1)] jobname= 'job'+'_'+str(number[0]) self.browser.find_by_id('jobName').fill(jobname) navigate[2].find_by_css('.next').click() navigate[3].find_by_css('.submit_btn').click() self.assertEqual(self.browser.find_by_tag('font').value, jobname) print self.browser.title() print self.browser.html unittest.main() 附录: Ubuntu 11.10 安装splinter 为了防止下面的过程出错,建议: sudo apt-get install build-essential python-dev libxml2-dev libxslt1-dev 1.下载splinter 0.4.7.tar.gz包 http://pypi.python.org/pypi/splinter/0.4.7/ 2.解压 tar -zxvf 安装包 进入cd splinter 0.4.7 3.安装 sudo python setup.py install ImportError: No module named setuptools wget http://pypi.python.org/packages/source/s/setuptools/setuptools-0.6c11.tar.gz tar zxvf setuptools-0.6c11.tar.gz cd setuptools-0.6c11 python setup.py build python setup.py install 安装过程中提示 make sure the development packages of libxml2 and libxslt are installed ** Using build configuration of libxslt src/lxml/lxml.etree.c:4: fatal error: Python.h: 没有那个文件或目录 compilation terminated. error: Setup script exited with error: command 'gcc' failed with exit status 1 经过google查询得知没有安装libxml2-dev和libxlst1-dev 为保险起见,请依次安装如下: sudo apt-get install gcc sudo apt-get install python-dev sudo apt-get install libxml2 libxml2-dev sudo apt-get install libxslt1.1 libxslt1-dev 后面的是数字1,不是字母l,不要写错了。 AttributeError: 'NoneType' object has no attribute 'clone' 到http://pypi.python.org/pypi/setuptools/ 下载ez_setup.py 执行python ez_setup.py -U setuptools 升级setuptools 过程中可能会出现网络的错误,如connection peer ,可以重复执行一下 支持浏览器fixfox,chrome,IE 还需下载安装http://pypi.python.org/pypi/selenium/ 下载selenium-2.25.0.tar.gz 解压 tar -zxvfselenium-2.25.0.tar.gz cd selenium-2.25.0 sudo python setup.py install 这样就可以模拟浏览器的操作 环境基本搭建好了,如果想用python去操作mysql数据库和oracle数据库, 还需安装: MySQLdb模块 和 cx_Oracle模块 安装MySQLdb模块: 首先安装MySQL数据库,在Ubuntu可以直接apt-get mysql,对于其他的系统,如Redhat 可以到oracle官网上下载mysql.rpm安装文件 使用命令rpm -ivh mysql-server.rpm 和rpm -ivh mysql-client.rpm 进行安装 假如发生包mysql-lib冲突,可以yum erase 或者yum remove 来把冲突的包给去掉。 ubuntu 系统:sudo apt-get install python-mysqldb 另外其他系统:下载 MySQL-python-1.2.3.tat.gz (下载地址Google下) 解压后 sudo python setup.py build 提示: ImportError: No module named setuptools (没有setuptools 模块) 继续下载 setuptools-0.6c11.tar.gz 解压后 sudo python setup.py build (编译) sudo python setup.py install (安装) 这回有 setuptools模块了吧! 回到用户MySQLdb源码目录 继续sudo python setup.py build 又提示:mysql_config not found 于是乎查mysql_config 得知mysql_config是属于MySQL开发用的文件,而使用apt-get安装的MySQL是没有这个文件的,于是在包安装器里面寻找 libmysqld-dev libmysqlclient-dev 这两个包安装后问题即可解决 这回/usr/bin/ 下有 mysql_config命令了 (查找命令 whereis mysql_config) 修改MySQLdb下的setup_posix.py 文件 找到mysql_config.path 改成mysql_config.path = "/usr/bin/mysql_config"//就是mysql_config.path=XXXX的这行。 在重复: sudo python setup.py build 又出错: error: command 'gcc' failed with exit status 1 sudo apt-get install build-essential sudo apt-get install python-dev 安完以后在回到MySQLdb目录 sudo python setup.py build (编译) sudo python setup.py install (安装) 安装cx_Oracle模块: Oracle Instant Client is a free Oracle database client. The current version is 11.2.0.1.0, and several versions back to 10.1.0.5 are available. Install RPMs Download the Oracle Instantclient RPM files fromhttp://www.oracle.com/technetwork/database/features/instant-client/index-097480.html. Everyone needs either "Basic" or "Basic lite", and most users will want "SQL*Plus" and the "SDK". Convert these .rpm files into .deb packages and install using "alien" ("sudo apt-get install alien" if you don't have it): 转为位deb包,然后直接安装 alien -i oracle-instantclient11.2-basic-11.2.0.3.0-1.x86_64.rpm alien -i oracle-instantclient11.2-devel-11.2.0.3.0-1.x86_64.rpm alien -i oracle-instantclient11.2-sqlplus-11.2.0.3.0-1.x86_64.rpm 由于是rpm包,在ubuntu上转换位deb包:sudo alien oracle-instantclient-basic-11.2.x86_64.rpm 得到deb包,在ubuntu上安装命令为:sudo dpkg -i oracle-instantclient-basic_11.2_amd64.deb Test your Instantclient install by using "sqlplus" to connect to your database: sqlplus username/password@//dbhost:1521/SID If sqlplus complains of a missing libaio.so.1 file, run sudo apt-get install libaio1 If sqlplus complains of a missing libsqlplus.so file, follow the steps in the section "Integrate Oracle Libraries" below. If you execute sqlplus and get "sqlplus: command not found", see the section below about adding the ORACLE_HOME variable. Integrate Oracle Libraries|ORACLE_HOME If oracle applications, such as sqlplus, are complaining about missing libraries, you can add the Oracle libraries to the LD_LIBRARY_PATH each time it is used, or to add it to the system library list create a new file as follows: sudo vim /etc/profile 增加以下环境变量: export ORACLE_HOME=/usr/lib/oracle/11.2/client64 export PATH=$PATH:$ORACLE_HOME/bin export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib export TNS_ADMIN=$ORACLE_HOME/network/admin source /etc/profile 文件 更新环境变量 如果需要配置tnsnames.ora还需要这样操作: sudo mkdir -p $ORACLE_HOME/network/admin sudo cp tnsnames.ora $ORACLE_HOME/network/admin Download and build cx_Oracle Download cx_Oracle at http://cx-oracle.sourceforge.net and unzip wherever you want, then... 选择源码安装:解压下载回来的tar.gz包,tar -zxvf cx_oracle.tar.gz cd [your cx_Oracle installation path] python setup.py build python setup.py install ImportError: libclntsh.so.11.1: cannot open shared object file: No such file or directory 此时是由于python在操作oracle数据库的时候需要用到oracle的一些库,而上面的问题就是说python需要的这些库不在环境的路径里,在linux上就是不在LD_LIBRARY_PATH环境变量里,此时时就需要把这些库路径加到LD_LIBRARY_PATH中 $exportLD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib/oracle/11.2/client64/lib 也可以选择rpm安装,解压出来里面有cx_Oracle.so ,执行命令:sudo cp cx_Oracle.so /usr/local/lib/python2.7/dist-packages/ 验证安装成功: luca@ubuntu:~$ python [GCC 4.4.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import cx_Oracle >>> print cx_Oracle.version 5.1.2