1 unittest基本概念
unittest是python自带的单元测试框架,有时候又被称为”PyUnit”,是python版本的JUint实现。
在学习使用unittest库之前,我们需要了解一下unittest库的一些重要概念:
test fixture: 代表了用例执行前的准备工作和用例执行之后的清理工作。比如在用例执行前创建临时文件和文件夹,又或者启动1个server进程等;
test case: 测试用例,这个相信大家都不陌生。是测试的最小单位,一般检查一组输入的响应(输出)是否符合预期。unittest模块提供了TestCase类来帮助我们创建测试用例;
test suite: 经常被翻译成”测试套件”,也有人称为”测试套”,是测试用例或测试套件的集合,一般用来把需要一起执行的用例组合到一起;
test runner: 用来执行测试用例并输出测试结果的组件。可以是图形界面或命令行界面;
总之:
test fixture的功能可以理解成是初始化和清理测试数据及环境
test case是测试用例
test suite是用例集合
test runner的作用是运行用例并返回结果
2 unittest基本用法
我们通过最简单的例子来看一下unittest的基本用法
import unittest
class Test(unittest.TestCase):
def setUp(self):
print('hello')
def tearDown(self):
print('bye')
def test_001(self):
print('001')
def test_002(self):
print('002')
def test_003(self):
print('003')
def func(self):
print('func')
if __name__ == '__main__':
unittest.main()
解释一下关键点:
可以通过继承unittest.TestCase类来定义我们自己的测试用例,1个测试用例类下面可以有多个测试方法(test)或者叫做测试点
测试用例中方法名以test开头的方法才是测试方法,比如上面的例子里定义了3个以test开头的方法,分别是test_001,test_002和test_003。非测试方法是不会被test runner执行的
setUp和tearDown这两个方法,这两个方法在测试方法执行前后会被自动调用,主要用作预处理和清理。这两个方法就是我们说的test fixture
通过上面的测试代码我们可以看到,在调用uniitest.main()的时候,框架自动的调用了Test类,因为Test继承自unittest.TesetCase类,所以会被框架识别为测试用例,
最后, unittest.main提供了最简单的运行用例的方式。
当我们从命令行运行上面的代码时,我们可以看到如下的输出
001
002
003
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
除了使用unittest.main,还有其他的方式可以运行测试用例,比如把最后2行替换为
# 创建一个测试套件,并向其中加载测试用例
suite = unittest.TestLoader().loadTestsFromTestCase(Test)
# 显式运行测试没并且通过设置verbosity设定对每一个测试方法产生测试结果
unittest.TextTestRunner(verbosity=2).run(suite)
运行用例,结果将会如下所示
001
002
003
test_001 (__main__.Test) ... ok
test_002 (__main__.Test) ... ok
test_003 (__main__.Test) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
3 各种断言方法
unittest断言
断言是测试用例的核心。我们使用assertEqual()来判断预期结果,用assertTrue()和assertFalse来做是非判断,以及用assertRaises()来判断预期的异常是否有被抛出。这些unittest提供的以assert开头的方法就是断言,一般情况下,每个测试方法里都必须有断言,unittest支持各种断言方法。
import unittest
class Test(unittest.TestCase):
def setUp(self):
print('hello')
def tearDown(self):
print('bye')
def test_001(self):
self.assertEqual('1', '1')
def test_002(self):
self.assertEqual('0', '1')
if __name__ == "__main__":
unittest.main()
断言列表
官方文档
方法 检查点
断言方法 | 含义 |
assertEqual(a, b) | a == b |
assertNotEqual(a, b) | a != b |
assertTrue(x) | bool(x) is True |
assertFalse(x) | bool(x) is False |
assertIs(a, b) | a is b |
assertIsNot(a, b) | a is not b |
assertIsNone(x) | x is None |
assertIsNotNone(x) | x is not None |
assertIn(a, b) | a in b |
assertNotIn(a, b) | a not in b |
assertIsInstance(a, b) | isinstance(a, b) |
assertNotIsInstance(a, b) | not isinstance(a, b) |
assertRaises(exc, fun, args, *kwds) | fun(args, *kwds) raises exc |
assertRaisesRegexp(exc, r, fun, args, *kwds) | fun(args, *kwds) raises exc and the message matches egex r |
assertAlmostEqual(a, b) | round(a-b, 7) == 0 |
assertNotAlmostEqual(a, b) | round(a-b, 7) != 0 |
assertGreater(a, b) | a > b 2.7 |
assertGreaterEqual(a, b) | a >= b |
assertLess(a, b) | a < b |
assertLessEqual(a, b) | a <= b |
assertRegexpMatches(s, r) | r.search(s) |
assertNotRegexpMatches(s, r) | not r.search(s) |
assertItemsEqual(a, b) | sorted(a) == sorted(b) 也支持unhashable对象 |
assertDictContainsSubset(a, b) | a里面所有的键值对都在b中存在 |
4 命令行接口
背景 unittest支持命令行接口,我们可以在命令行里指定运行具体的测试用例。
实例 在test.py中定义了Test1和Test2用例,我们可以从命令行中指定只运行该用例。
import unittest
class Test1(unittest.TestCase):
def setUp(self):
print('Test1')
def tearDown(self):
print('Test1')
def test_001(self):
print('测试Test1 $$$$$$$$$')
class Test2(unittest.TestCase):
def setUp(self):
print('Test2')
def tearDown(self):
print('Test2')
def test_001(self):
print('测试Test2 ########')
if __name__ == '__main__':
unittest.main()
在命令行中执行
python -m unittest test.Tese1
python -m unittest test.Tese2
还可以使用-v参数来获得更详细的输出
$ python -m unittest test.Tese1 -v
5 unittest与selenium
封装待测试功能的操作
1.完成创建Commonlib目录,创建Commonlib.py文件,并将前面封装的selenium拷贝进去
from selenium import webdriver
import time
class Commonshare(object):
# 初始化方法
def __init__(self):
# 创建浏览器对象
self.driver = webdriver.Firefox()
# 设置隐式等待
self.driver.implicitly_wait(5)
# 设置浏览器的最大化
self.driver.maximize_window()
def open_url(self,url):
# 请求指定站点
self.driver.get(url)
time.sleep(3)
def locateElement(self, locate_type, value):
# 判断定位方式并调用相关方法
el = None
if locate_type == 'id':
el = self.driver.find_element_by_id(value)
elif locate_type == 'name':
el = self.driver.find_element_by_name(value)
elif locate_type == 'class':
el = self.driver.find_element_by_class_name(value)
elif locate_type == 'text':
el = self.driver.find_element_by_link_text(value)
elif locate_type == 'xpath':
el = self.driver.find_element_by_xpath(value)
elif locate_type == 'css':
el = self.driver.find_element_by_css_selector(value)
# 如果el不为None,则返回
if el is not None:
return el
# 指定对某一元素的点击操作
def click(self, locate_type, value):
# 调用自己创建的locateElement方法定位元素
el = self.locateElement(locate_type,value)
el.click()
time.sleep(1)
# 对指定的元素进行数据输入
def input_data(self,locate_type,value,data):
# 调用自己创建的locateElement方法定位元素
el = self.locateElement(locate_type,value)
el.send_keys(data)
# 获取指定元素的文本内容
def get_text(self, locate_type, value):
# 调用自己创建的locateElement方法定位元素
el = self.locateElement(locate_type, value)
return el.text
# 获取指定元素的属性值
def get_attr(self, locate_type, value, data):
# 调用自己创建的locateElement方法定位元素
el = self.locateElement(locate_type, value)
return el.get_attribute(data)
# 收尾清理方法
def __del__(self):
time.sleep(3)
self.driver.quit()
- 创建Busniess目录,创建Business.py文件,并编写如下
# 导入已经封装好selenium的CommonShare类
from CommonLib.Commonlib import Commonshare
# 继承CommonShare,继承之后Commonshare中的方法都能使用
class Login(Commonshare):
def login_yhd(self, user, pwd):
self.open_url('http://www.yhd.com')
# 点击登陆
self.click('class', 'hd_login_link')
# 输入账号
self.input_data('id', 'un', user)
# 输入密码
self.input_data('id', 'pwd', pwd)
# 点击登陆
self.click('id', 'login_button')
if __name__ == '__main__':
login = Login()
login.login_yhd('xxxxxxx','xxxxxx')
- 创建Testcase目录编写测试用例testcase.py文件,如下
import unittest
# 从Business组件中导入我们定义的测试登录的类
from Busniess.Login import Login
class Test(unittest.TestCase):
def setUp(self):
print ("开始进行测试")
def tearDown(self):
print('测试结束')
# 正确登录的测试用例
def test_001(self):
login = Login()
login.login_yhd('xxxxx','xxxxx')
data = login.get_text('class','hd_login_name')
self.assertEqual('hack_ai_buster',data)
# 没有输账号密码直接点击登录的测试用例
def test_002(self):
login = Login()
login.login_yhd('','')
data = login.get_text('id','error_tips')
self.assertEqual('请输入账号和密码',data)
# 随意输入账号,不输入密码的测试用例
def test_003(self):
login = Login()
login.login_yhd('hgvjchgc','')
data = login.get_text('id','error_tips')
self.assertEqual('请输入密码',data)
# 用于演示出bug的测试用例
def test_004(self):
login = Login()
login.login_yhd('hgvjchgc','')
data = login.get_text('id','error_tips')
# 我们故意写错预期结果,让这个断言抛出
self.assertEqual('请输入密码itcast',data)
if __name__ == '__main__':
unittest.main()
4.在Testcase目录编写测试用例testcase.py文件,如下
import unittest
from Testcase.testcase import Test
class Suit(unittest.TestCase):
def test_suit(self):
# 定义要是用的测试用例列表
case_list = ['test_002','test_003','test_004']
# 创建测试套件
mysuit = unittest.TestSuite()
# 遍历测试用例列表
for case in case_list:
# 将测试用例添加到测试套件中
mysuit.addTest(Test(case))
# 运行Runner执行测试,verbosity=2指定为每个测试用例生成报告,run中传入要执行的测试套件
unittest.TextTestRunner(verbosity=2).run(mysuit)
if __name__ == '__main__':
unittest.main()