- 测试固件每次都执行,setUp和tearDown的测试固件
from selenium import webdriver
import unittest
import time
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_baidu_news(self):
'''验证:测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
time.sleep(2)
def test_baidu_map(self):
'''验证:测试百度首页点击地图后的跳转'''
self.driver.find_element_by_link_text('地图').click()
time.sleep(2)
if __name__ == '__main__':
unittest.main(verbosity=2)
- 测试固件只执行一次
from selenium import webdriver
import unittest
import time
class BaiduTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
cls.driver.maximize_window()
cls.driver.get('http://www.baidu.com')
cls.driver.implicitly_wait(30)
@classmethod
def tearDownClass(cls):
cls.driver.quit()
def test_baidu_news(self):
'''验证:测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
time.sleep(2)
self.driver.get('http://www.baidu.com')
def test_baidu_map(self):
'''验证:测试百度首页点击地图后的跳转'''
self.driver.find_element_by_link_text('地图').click()
time.sleep(2)
self.driver.get('http://www.baidu.com')
if __name__ == '__main__':
unittest.main(verbosity=2)
- 构建测试套件
3.1 测试用例按顺序执行
要用到TestSuite类的addTest()方法
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_baidu_news(self):
'''验证:测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
url = self.driver.current_url
self.assertEqual(url, 'http://news.baidu.com/')
def test_baidu_map(self):
'''验证:测试百度首页点击地图后的跳转'''
self.driver.find_element_by_link_text('地图').click()
self.driver.get('http://www.baidu.com')
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(BaiduTest('test_baidu_news'))
suite.addTest(BaiduTest('test_baidu_map'))
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
默认的执行顺序是TestCaseName的ASCII码顺序,调用addTest()方法后,先添加的先执行,后添加的后执行。
但是这里addTest方法没有起到作用,还是先执行test_baidu_map,后执行test_baidu_news。原因是:在pycharm中,引入了unittest模块,会默认按照unittest模式执行。需要将unittest模式转换成普通模式。 解决方法见文章:https://blog.csdn.net/qq_33356414/article/details/94556140
3.2 按测试类执行
用makeSuite方法,把所有的测试用例组成测试套件,避免一个一个添加测试用例的繁琐过程。
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_baidu_news(self):
'''验证:测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
url = self.driver.current_url
self.assertEqual(url, 'http://news.baidu.com/')
def test_baidu_map(self):
'''验证:测试百度首页点击地图后的跳转'''
self.driver.find_element_by_link_text('地图').click()
self.driver.get('http://www.baidu.com')
if __name__ == '__main__':
suite = unittest.TestSuite(unittest.makeSuite(BaiduTest))
unittest.TextTestRunner(verbosity=2).run(suite)
3.3 加载测试类
suite = unittest.TestLoader().loadTestsFromTestCase(BaiduTest) 用TestLoader()类加载。
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_baidu_news(self):
'''验证:测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
url = self.driver.current_url
self.assertEqual(url, 'http://news.baidu.com/')
def test_baidu_map(self):
'''验证:测试百度首页点击地图后的跳转'''
self.driver.find_element_by_link_text('地图').click()
self.driver.get('http://www.baidu.com')
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(BaiduTest)
unittest.TextTestRunner(verbosity=2).run(suite)
3.4 按测试模块执行
suite = unittest.TestLoader().loadTestsFromModule(‘sy_unittest.py’)
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_title(self):
"""验证:测试百度浏览器的title"""
self.assertEqual(self.driver.title, '百度一下,你就知道')
def test_002(self):
"""验证:测试百度首页点击新闻后的跳转"""
self.driver.find_element_by_link_text('新闻').click()
url = self.driver.current_url
self.assertEqual(url, 'http://news.baidu.com/')
@unittest.skip('do not run')
def test_baidu_003(self):
"""验证:测试百度首页点击地图后的跳转"""
self.driver.find_element_by_link_text('地图').click()
class BaiduMap(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_baidu_map(self):
"""验证:测试百度首页点击地图后的跳转"""
self.driver.find_element_by_link_text('地图').click()
self.driver.get('http://www.baidu.com')
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromModule('sy_unittest.py')
unittest.TextTestRunner(verbosity=2).run(suite)
3.5 优化测试套件
可以单独把测试套件写成一个方法来调用。
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_news(self):
"""验证:测试百度首页点击新闻后的跳转"""
self.driver.find_element_by_link_text('新闻').click()
url = self.driver.current_url
self.assertEqual(url, 'http://news.baidu.com/')
def test_baidu_map(self):
"""验证:测试百度首页点击地图后的跳转"""
self.driver.find_element_by_link_text('地图').click()
self.driver.get('http://www.baidu.com')
@staticmethod
def suite(testCaseClass):
suite = unittest.TestLoader().loadTestsFromTestCase(testCaseClass)
return suite
if __name__ == '__main__':
unittest.TextTestRunner(verbosity=2).run(BaiduTest.suite(BaiduTest))
- 分离测试固件
测试固件用来在测试开始时初始化WebDriver类及打开浏览器,在测试结束后关闭浏览器。可以单独分离出来写,新建init.py模块,类名称为InitTest。
import unittest
from selenium import webdriver
class InitClass(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
将测试固件分离出来后,测试类继承InitTest,在测试类的文件中直接编写要执行的测试用例。
import unittest
from init import InitClass
class BaiduTest(InitClass):
def test_news(self):
"""验证:测试百度首页点击新闻后的跳转"""
self.driver.find_element_by_link_text('新闻').click()
url = self.driver.current_url
self.assertEqual(url, 'http://news.baidu.com/')
def test_baidu_map(self):
"""验证:测试百度首页点击地图后的跳转"""
self.driver.find_element_by_link_text('地图').click()
self.driver.get('http://www.baidu.com')
@staticmethod
def suite(testCaseClass):
suite = unittest.TestLoader().loadTestsFromTestCase(testCaseClass)
return suite
if __name__ == '__main__':
unittest.TextTestRunner(verbosity=2).run(BaiduTest.suite(BaiduTest))
把测试固件分离的好处是,在后续的测试中,如果需要改变测试地址,只需要修改init模块中的URL地址即可,减少了编写重复性代码的开销。
5. 测试断言
断言就是判断实际测试结果是否与预期结果一致。每一个测试用例必须要有断言。
在TestCase类中提供了assert方法来检查和报告失败。
assertEqual(a, b)方法用于测试两个值是否相等,不仅要内容相同还要类型相同。
assertTrue(a) 验证布尔类型,要求返回的是True
assertFalse(a) 验证布尔类型,要求返回的是False
assertIn(a,b) 判断a是否包含在b内
6. 断言的注意事项
注意⚠️在自动化测试中,尽量不要通过打印结果来判断测试用例的情况。用例如果错误或者功能有bug的情况下就让用例报错失败,只有功能正常正确的测试用例结果才会是pass的。
切记不要使用if…else判断来代替断言,因为这样不管是if else的哪种情况,测试用例都会通过,显示pass
也切记不要用try…except异常来判断用例,这样结果总会是pass.
- 批量执行测试用例
新建文件allTests.py,在allTests.py中编写批量执行的代码。test_baidu.py和test_sina.py,放在allTests.py同一个文件路径下。
test_baidu.py文件如下:
from selenium import webdriver
import unittest
class BaiduTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('http://www.baidu.com')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_baidu_news(self):
'''验证:测试百度首页点击新闻后的跳转'''
self.driver.find_element_by_link_text('新闻').click()
url = self.driver.current_url
self.assertEqual(url, 'http://news.baidu.com/')
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(BaiduTest('test_baidu_news'))
suite.addTest(BaiduTest('test_baidu_map'))
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
test_sina.py文件如下:
from selenium import webdriver
import unittest
class SinaTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get('http://mail.sina.com.cn/')
self.driver.implicitly_wait(30)
def tearDown(self):
self.driver.quit()
def test_username_password_null(self):
"""验证:新浪登录页面用户名和密码为空提示错误信息"""
self.driver.find_element_by_id('freename').send_keys('')
self.driver.find_element_by_id('freepassword').send_keys('')
self.driver.find_element_by_link_text('登录').click()
divError = self.driver.find_element_by_xpath('/html/body/div[1]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]').text
self.assertEqual(divError, '请输入邮箱名')
if __name__ == '__main__':
unittest.main(verbosity=2)
allTests.py文件如下:
import unittest
import os
def allCases():
"""获取所有的测试"""
suite = unittest.TestLoader().discover(start_dir=os.path.dirname(__file__), pattern='test_*.py', top_level_dir=None)
# os.path.dirname(__file__) 表示获取当前文件的路径
return suite
if __name__ == '__main__':
unittest.TextTestRunner(verbosity=2).run(allCases())
批量获取测试用例,用到的是discover()方法,第一个参数start_dir表示测试模块的路径,os.path.dirname(__file__)
表示获取当前文件的路径, 第二个参数pattern用来获取testCase包中所有以test开头的模块文件;第三个参数top_level_dir默认值为None.
运行allTests.py文件,得到的结果如图。
8. 生成测试报告
生成测试报告,需要借助第三方库生成HTML格式的测试报告,这里用的库是HTMLTestRunner.py, 下载地址http://tungwaiyip.info/software/HTMLTestRunner.html
下载完HTMLTestRunner.html文件后,将该文件放到Python安装路径的Lib文件夹中。(这里直接放到了测试文件同一个目录下)
在testCase同一个目录下,创建文件夹report,用来存放生成的测试报告。
注意⚠️上面的HTMLTestRunner文件是用于python2的,在python3中会报很多错,在网上找到了这份适用于python3的:https://pan.baidu.com/s/1W6e_Bqg9dZTkVOWUP93XkA,放到目录下后,运行成功。
修改allTests.py文件
import unittest
import os
import HTMLTestRunner
import time
def allCases():
"""获取所有的测试"""
suite = unittest.TestLoader().discover(start_dir=os.path.dirname(__file__), pattern='test_*.py', top_level_dir=None)
# os.path.dirname(__file__) 表示获取当前文件的路径
return suite
def getNowTime():
"""获取当前时间"""
return time.strftime('%Y-%m-%d %H_%M_%S', time.localtime(time.time()))
def run():
fileName = os.path.join(os.path.dirname(__file__), 'report', getNowTime()+'report.html')
fp = open(fileName, 'wb')
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title='UI自动化测试报告', description='UI自动化测试报告详细信息')
runner.run(allCases())
if __name__ == '__main__':
run()
allTests.py文件中导入了库os、HTMLTestRunner库。run()方法用来执行测试套件中的测试用例,并且生成测试报告。
##以下可以忽略。下载了python3版本的HTMLTestRunner.html文件就没有问题了。
运行之后,报错ModuleNotFoundError: No module named ‘StringIO’,
显示没有安装StringIO模块,在pycharm中安装,但是查不到,发现可能是和版本有关。
在python 2.x版本中,import StringIO可以直接导入,但是python3中,StringIO已经不存在了,要这样写:
from io import StringIO
改好后运行,又报错了:AttributeError: type object ‘_io.StringIO’ has no attribute ‘StringIO’
生成的测试报告如图:
9. 代码覆盖率统计
安装coverage模块,运行coverage3 run allTests.py命令,再运行coverage html命令。
就可以生成覆盖率统计报告了。
打开index.html文件可以看到所有file的覆盖率总览。