一篇文章搞懂unittest单元测试框架
每天进步一点点,关注我们哦,每天分享测试技术文章
码同学公众号:自动化软件测试
码同学抖音号:小码哥聊软件测试
Python 2.1及以后的版本,将 unittest 作为一个标准模块放入Python开发包中。
01 使用unittest编写测试用例
规则: import unittest 创建一个测试类,必须要继承unittest.TestCase类 创建一个测试方法,且方法要以"test" 开头 from calculator import Calculator import unittest class TestAdd(unittest.TestCase): def test_add(self): c =Calculator(3,5) result = c.add() self.assertEqual(result,8) if __name__ =="__main__": unittest.main() //通过main()方法来执行测试用例;按照测试类、方法的名称ASCII值大小的顺序执行用例
unittest的执行结果 : "." 表示测试用例执行通过 "F" 表示执行失败 "E" 表示执行错误 "s" 表示运行跳过
02 三个重要概念
Test Case 最小的测试单元,即 测试方法 。 unittest提供了TestCase基类,我们创建的测试类要继承该基类,它可以 用来创建新的测试用例 。
Test Suite 测试用例、测试套件或两者的集合,用于 组装一组要运行的测试。
使用TestSuite类来创建测试套件。
Test Runner Test Runner是一个组件, 用于协调测试的执行并向用户提供结果。 unittest提供了TextTestRunner类运行测试用例。
03 测试用例执行顺序
unittest默认按照 ASCII码 的顺序加载测试用例(包括测试目录和测试文件、测试类、测试方法),即 它并不是按照测试用例的创建顺序从上到下执行的。
discover() 和 main() 方法的执行顺序是一样的。故想让某个测试文件先执行,可以在命名上加以控制。
如何控制测试用例的执行顺序? 可以通过TestSuite类的 addTest() 方法按照一定的顺序来加载测试用例,这样想先被执行的用例就可以先加载。 from calculator import Calculator import unittest class TestAdd(unittest.TestCase): def test_add(self): c =Calculator(3,5) result = c.add() self.assertEqual(result,8) def test_add_decimals(self): c=Calculator(3.5,5.5) result=c.add() self.assertEqual(result,9) class TestSub(unittest.TestCase): def test_sub(self): c =Calculator(5,1) result = c.sub() self.assertEqual(result,4) if __name__ =="__main__": #创建测试套件 suit = unittest.TestSuite() suit.addTest(TestSub("test_sub")) //添加测试用例 suit.addTest(TestAdd("test_add_decimals")) #创建测试运行器 runner = unittest.TextTestRunner() runner.run(suit)
04 执行多个测试用例
unittest.defaultTestLoader.discover() 方法可以从多个文件中查找测试用例。
该类根据各种标准加载测试用例,并将它们返回给测试套件
discover ( start_dir , pattern =" Test*.py ", top_level_dir = None ) start_dir: 待测试的模块名/测试用例目录; discover() 方法会自动根据这个参数查找测试用例文件 pattern: 测试用例文件名的匹配原则 top_level_dir: 测试模块的顶级目录,如果没有顶级目录,默认为 None import unittest test_dir="Test" //文件目录 suits =unittest.defaultTestLoader.discover(test_dir, pattern="Test*.py") if __name__=="__main__": runner = unittest.TextTestRunner() runner.run(suits)
如果想让 discover() 查找子目录下的测试文件,得将子目录标记为一个python模块(子目录下放__init__.py 文件)
05 跳过测试和预期失败
import unittest class MyTest(unittest.TestCase): @unittest.skip("跳过这条用例") def test_skip(self): print("aaa") @unittest.skipIf(3>2,"当条件为真时跳过测试") def test_skip_if(self): print("bbb") @unittest.skipUnless(3>2,"当条件为假时跳过测试") def test_skip_unless(self): print("ccc") // 不论执行结果是什么,都将测试标记为失败 @unittest.expectedFailure def test_fail(self): print("ddd") if __name__=="__main__": unittest.main()
执行结果: E:selenium> & "D:/Program Files/Python39/python.exe" e:/selenium/Test/unittest/skip_fail.py ddd ussccc . ---------------------------------------------------------------------- Ran 4 tests in 0.001s FAILED (skipped=2, unexpected successes=1)
以上四个装饰器同样适用于测试类。
06 Setup和Teardown
setUpModule/tearDownModule 在 整个模块 的开始与结束时被执行 setUpClass/ tearDownClass 在 测试类 的开始与结束时被执行 setUp/tearDown 在 测试用例 的开始与结束时被执行 import unittest def setUpModule(): print(" Module start .....") def tearDownModule(): print(" Module end ...") class MyTest(unittest.TestCase): @classmethod def setUpClass(cls): print("Class Start...") @classmethod def tearDownClass(cls): print("Class end...") def setUp(self): print("test case start ...") def tearDown(self): print("test case end ...") def testcase1(self): print("this is first test case") def testcase2(self): print("this is second test case") if __name__ == "__main__": unittest.main()
执行结果如下: E:selenium> & "D:/Program Files/Python39/python.exe" e:/selenium/Test/unittest/setup.py Module start ..... Class Start... test case start ... this is first test case test case end ... .test case start ... this is second test case test case end ... .Class end... Module end ... ---------------------------------------------------------------------- Ran 2 tests in 0.002s OK
免费领取 码同学软件测试 课程笔记+超多学习资料+完整视频+最新面试题,可以转发文章 + 私信「码同学666」获取资料哦
07 Web自动化测试
import unittest from selenium import webdriver from time import sleep class Baidu(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver = webdriver.Chrome() cls.driver.implicitly_wait(5) cls.driver.get("http://www.baidu.com") cls.driver.maximize_window() @classmethod def tearDownClass(cls): cls.driver.quit() def search(self,text): self.driver.find_element_by_id("kw").clear() self.driver.find_element_by_id("kw").send_keys(text) self.driver.find_element_by_id("su").click() sleep(3) def test_search_selenium(self): search_key="selenium" self.search(search_key) self.assertEqual(self.driver.title,search_key+"_百度搜索") def test_search_python(self): search_key="python" self.search(search_key) self.assertEqual(self.driver.title,search_key+"_百度搜索") if __name__ =="__main__": unittest.main()
08
Parameterized
Parameterized 是python的一个参数化库,同时 支持unittest、pytest单元测试框架。 pip install parameterized parameterized.expand() 加载数据,列表中 每个元组是一条测试用例 import unittest from time import sleep from selenium import webdriver from parameterized import parameterized class TestBaidu(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver=webdriver.Chrome() cls.driver.get("http://www.baidu.com") cls.driver.maximize_window @classmethod def tearDownClass(cls): cls.driver.quit() def search(self,search_key): self.driver.find_element_by_id("kw").clear() self.driver.find_element_by_id("kw").send_keys(search_key) self.driver.find_element_by_id("su").click() sleep(5) @parameterized.expand( [ ("case1","selenium"), ("case2","python") ] ) def test_search(self,name,search_key): self.search(search_key) self.assertEqual(self.driver.title,search_key+"_百度搜索") if __name__ == "__main__": unittest.main(verbosity=2)
09
DDT
Data-Driven Tests 是 针对unittest单元测试框架 设计的扩展库。
安装:
pip install ddt
导入:
from ddt import ddt, data, file_data,unpack
使用规则: 必须用 @ddt 装饰测试类 数据有不同形式的参数化,如下方的元组、列表、字典类型 @data ( 数据列表/元组/字典,字典的key和方法中的形参名称保持一致) @unpack 装饰测试方法 import unittest from time import sleep from selenium import webdriver from ddt import ddt,data,file_data,unpack @ddt class TestBaidu(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver=webdriver.Chrome() cls.driver.get("http://www.baidu.com") cls.driver.maximize_window @classmethod def tearDownClass(cls): cls.driver.quit() def search(self,search_key): self.driver.find_element_by_id("kw").clear() self.driver.find_element_by_id("kw").send_keys(search_key) self.driver.find_element_by_id("su").click() sleep(5) @data( ("case1","selenium"), ("case2","python") ) @unpack def test_search1(self,name,search_key): print("第一组测试用例:",name) self.search(search_key) self.assertEqual(self.driver.title,search_key+"_百度搜索") @data( ["case1","selenium"], ["case2","python"] ) @unpack def test_search2(self,name,search_key): print("第二组测试用例:",name) self.search(search_key) self.assertEqual(self.driver.title,search_key+"_百度搜索") @data( {"key":"selenium"}, {"key":"python"} ) @unpack def test_search3(self,key): print("第三组测试用例:",key) self.search(key) self.assertEqual(self.driver.title,key+"_百度搜索") if __name__ == "__main__": unittest.main(verbosity=2)
执行报错如下时,是 因为文件名也为ddt: PS E:selenium> & "D:/Program Files/Python39/python.exe" e:/selenium/Test/unittest/ddt.py Traceback (most recent call last): File "e:seleniumTestunittestddt.py", line 4, in from ddt import ddt,data,file_data,unpack File "e:seleniumTestunittestddt.py", line 4, in from ddt import ddt,data,file_data,unpack ImportError: cannot import name "ddt" from partially initialized module "ddt" (most likely due to a circular import) (e:seleniumTestunittestddt.py)
10
数据文件的参数化
@file_data() 装饰器中内容为文件名称。支持 json格式 和yaml格式 。 import unittest from time import sleep from selenium import webdriver from ddt import ddt,data,file_data,unpack @ddt class TestBaidu(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver=webdriver.Chrome() cls.driver.get("http://www.baidu.com") cls.driver.maximize_window @classmethod def tearDownClass(cls): cls.driver.quit() def search(self,search_key): self.driver.find_element_by_id("kw").clear() self.driver.find_element_by_id("kw").send_keys(search_key) self.driver.find_element_by_id("su").click() sleep(5) @file_data("ddt_data.json") @unpack def test_search1(self,search_key): print("第一组测试用例:",search_key) self.search(search_key) self.assertEqual(self.driver.title,search_key+"_百度搜索") @file_data("ddt_data_yml.yaml") @unpack def test_search2(self,case): search_key=case[0]["search_key"] print("第二组测试用例:",search_key) self.search(search_key) self.assertEqual(self.driver.title,search_key+"_百度搜索") if __name__ == "__main__": unittest.main(verbosity=2) ddt_data.json { "case1": {"search_key": "python"}, "case2": {"search_key": "ddt"} } ddt_data_yml.yaml case1: - search_key: "python" case2: - search_key: "ddt"
END
免费领取码同学软件测试课程笔记+超多学习资料+学习完整视频,可以关注我们公众号哦:自动化软件测试