unittest は Python の単体テスト フレームワークで、主に次の機能があります。テストケースの数が少ない場合は、テストケースの構成を考える必要はありませんが、テストケースが数百、数千に達すると、大量のテストケースが積み重なって問題が発生します。スケーラビリティや保守性など、ユースケースの仕様と構成を検討する時期が来ています。この問題を解決するために単体テスト フレームワークが登場しました。
豊富な比較方法を提供します。ユース ケースの実行後、実際の結果を期待される結果 (アサーション) と比較して、ユース ケースがスムーズに通過できるかどうかを判断する必要があります。単体テストは通常、豊富なアサーション メソッドを提供します。たとえば、等しい/不等、包含/除外、True/False などのアサーション方法です。
豊富なログを提供します。テスト ケースの実行が失敗した場合、失敗の明確な理由がスローされ、すべてのテスト ケースが実行された場合、豊富な実行結果が提供されます。たとえば、合計実行時間、失敗したユースケースの数、成功したユースケースの数などです。
単体テスト、テスト フィクスチャ、テスト ケース、テスト スイート、テスト ランナーには 4 つの非常に重要な概念があります。
テスト フィクスチャ
テスト ケース環境の構築と破棄はフィクスチャであり、setUp() メソッドと TearDown() メソッドをオーバーライドすることで実現されます。
setUp() メソッドは、テスト対象のブラウザのドライバーを取得するなど、テスト環境をセットアップするために使用できます。また、テスト中にデータベースにアクセスする必要がある場合は、次のようにしてデータベースを初期化できます。 setUp() でデータベース接続を確立します。
tearDown() メソッドは環境を破壊し、ブラウザを閉じたり、データベース接続を閉じたり、データベースで生成されたデータをクリアしたりすることができます。テスト ケース
TestCase インスタンスはテストです。場合 。テスト ケースは、テスト前の準備環境の確立 (setUp)、テスト プロセスを実装するコード、テスト後の環境の復元 (tearDown) を含む、完全なテスト プロセスです。単体テスト(ユニットテスト)の本質はここにある テストケースとは、ある機能を検証できる完成したテスト単位です。テスト スイート
関数の検証には、一緒に実行できる複数のテスト ケースが必要になることが多く、これがテスト スイート TestSuite の概念を生み出します。テスト スーツは、複数のテスト ケースを組み立てるために使用されます。
テスト ランナー テストの実行も非常に重要な概念です。unittest フレームワークでは、テスト スイートは、 TextTestRunner クラスのテスト ケース。
from selenium import webdriver import unittest import time import os from selenium.common.exceptions import NoAlertPresentException from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.common.by import By class Baidu1(unittest.TestCase): def setUp(self): print("-----setUp-----") self.driver = webdriver.Chrome() self.url = "https://www.baidu.com/" self.driver.maximize_window() time.sleep(3) def tearDown(self): print("-----tearDown-----") self.driver.quit() def test_hao(self): print("111111111") driver = self.driver url = self.url driver.get(url) driver.find_element(By.LINK_TEXT,"hao123").click() time.sleep(6) def test_hbaidu(self): print("22222222") driver = self.driver url = self.url driver.get(url) driver.find_element(By.ID,"kw").send_keys("unittest") driver.find_element(By.ID,"su").submit() time.sleep(5) print(driver.title) # self.assertNotEqual(driver.title, "百度一下_百度搜索", msg="不相等") # self.assertTrue("beautiful"=="beauty", msg="Not Equal!") time.sleep(6) def saveScreenAsPhoto(self, driver, file_name): if not os.path.exists("./image"): os.makedirs("./image") now = time.strftime("%Y%m%d-%H%M%S", time.localtime(time.time())) driver.get_screenshot_as_file("./image/" + now + "-" + file_name) time.sleep(3) print("3333333") if __name__ == "__main__": unittest.main()
このスクリプトのクラス Baidu1 は、unittest.TestCase クラスを継承しているため、unittest フレームワークを使用してテスト ケース (TestCase) を整理します。
setUp() と setDown() は、unittest フレームワークのテスト ファームウェアです。
test_ で始まる名前のメソッドはテスト メソッドであり、クラス全体の実行時にデフォルトで実行されます。
unittest は、単体テスト モジュールを直接実行できるテスト スクリプトに簡単に変換できるグローバル main() メソッドを提供します。
main() メソッドは、このモジュールに含まれる「test」という名前のすべてのテスト メソッドを検索し、自動的に実行します。
2 スクリプトのバッチ実行
testbaidu1.py と testbaidu2.py
testbaidu2.py
from selenium import webdriver import unittest import time from selenium.common.exceptions import NoAlertPresentException from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.common.by import By class Baidu2 (unittest.TestCase) : def setUp(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(30) self.base_url = "http://www.baidu.com/" self.driver.maximize_window() self.verificationErrors=[] self.accept_next_alert = True def tearDown(self): self.driver.quit() self.assertEqual([], self.verificationErrors) def test_hao(self): driver = self.driver driver.get(self.base_url) driver.find_element(By.LINK_TEXT,"新闻").click() time.sleep(6) self.assertTrue("123" == "1234", msg="not true") time.sleep(3) def test_baidusearch(self): driver = self.driver driver.get(self.base_url) driver.find_element(By.ID,"kw").clear() driver.find_element(By.ID,"kw").send_keys(u"unittest") driver.find_element(By.ID,"su").click() time.sleep(6) def is_element_present(self, how, what): try: self.driver.find_element(by=how, value=what) except NoSuchElementException as e: return False return True def is_alert_present(self): try: self.driver.switch_to.alert except NoAlertPresentException as e: return False return True def close_alert_and_get_its_text(self): try: alert = self.driver.switch_to.alert alert_text = alert.text if self.accept_next_alert: alert.accept() else: alert.dismiss() return alert_text finally: self.accept_next_alert = True if __name__ == "__main__": unittest.main(verbosity=2)
という 2 つのファイルを作成したと仮定します。 addTest()
TestSuite クラスの addTest() メソッドは、さまざまなテスト クラスのテスト メソッドをテスト スイートにアセンブルできますが、addTest() は一度にクラス内の 1 つのテストのみをアセンブルできます。テストスイート。方法は次のとおりです。
testbaidu1.py と testbaidu2.py のテスト メソッドをテスト スイートに配置し、testsuite.py に実装します。
testsuite.py
import unittest from testUnittest import testbaidu1 from testUnittest import testbaidu2 def createsuite(): #addTest suite = unittest.TestSuite() suite.addTest(testbaidu1.Baidu1("test_hao")) suite.addTest(testbaidu1.Baidu1("test_hbaidu")) suite.addTest(testbaidu2.Baidu2("test_hao")) suite.addTest(testbaidu2.Baidu2("test_baidusearch")) return suite if __name__=="__main__": suite = createsuite() runner = unittest.TextTestRunner(verbosity=2) runner.run(suite)
ただし、上記のアプローチには 2 つの不便さがあり、スクリプトの迅速な実行が妨げられます。Testsuite.py は毎回変更する必要があります。 import testbaidu1 などの関連する py ファイルをすべてインポートするには、新しいスクリプトごとに
addTest は一度に 1 つのテスト メソッドしか追加できません。py ファイルに 10 個のテスト メソッドがある場合、テスト スイートでは、
makeSuite() と TestLoader() の 10 個のアプリケーションを追加する必要があります。
makeSuite() メソッドは、unittest フレームワークで提供されます。テスト ケース クラス すべてのテスト ケースは、TestSuite と呼ばれるテスト スイートで構成されており、unittest が makeSuite を呼び出すときに、テスト クラス名を渡すだけで済みます。
TestLoader は、クラスとモジュールのテスト スイートを作成するために使用されます。一般に、TestLoader().loadTestsFromTestCase(TestClass) は、テスト クラスをロードするために使用されます。
runall.py
import unittest,csv import os,sys import time import testbaidu1 import testbaidu2 #手工添加案例到套件, def createsuite(): suite = unittest.TestSuite() #将测试用例加入到测试容器(套件)中 suite.addTest(unittest.makeSuite(testbaidu1.Baidu1)) suite.addTest(unittest.makeSuite(testbaidu2.Baidu2)) return suite ''' suite1 = unittest.TestLoader().loadTestsFromTestCase(testbaidu1.Baidu1) suite2 = unittest.TestLoader().loadTestsFromTestCase(testbaidu2.Baidu2) suite = unittest.TestSuite([suite1, suite2]) return suite ''' if __name__=="__main__": suite=createsuite() runner = unittest.TextTestRunner(verbosity=2) runner.run(suite)
makeSuite() と TestLoader() の導入後は、クラスをテストするために py ファイルは必要なくなり、一度インポートするだけで済みます。
discover() アプリケーション
discover 是通过递归的方式到其子目录中从指定的目录开始, 找到所有测试模块并返回一个包含它们对象的TestSuite ,然后进行加载与模式匹配唯一的测试文件,discover 参数分别为discover(dir,pattern,top_level_dir=None)
runall.py—注意路径
import unittest,csv import os,sys import time #手工添加案例到套件, def createsuite(): discover=unittest.defaultTestLoader.discover("../testUnittest",pattern='test*.py',top_level_dir=None) print (discover) return discover if __name__=="__main__": suite=createsuite() runner = unittest.TextTestRunner(verbosity=2) runner.run(suite)
unittest 框架默认加载测试用例的顺序是根据ASCII 码的顺序,数字与字母的顺序为: 0 ~ 9,A ~ Z,a ~ z 。
对于测试目录与测试文件来说, unittest 框架同样是按照这个规则来加载测试用例
语法:
@unittest.skip(u'The function was canceled, neglects to perform thecase')
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import Select from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoAlertPresentException import unittest, time, re class Baidu1(unittest.TestCase): #test fixture,初始化环境 def setUp(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(30) self.base_url = "http://www.baidu.com/" self.verificationErrors = [] self.accept_next_alert = True @unittest.skip("skipping") def test_baidusearch(self): driver = self.driver driver.get(self.base_url + "/") driver.find_element(By.ID,"kw").click() driver.find_element(By.ID,"kw").clear() driver.find_element(By.ID,"kw").send_keys(u"测试") driver.find_element(By.ID,"su").click() driver.find_element(By.ID,"su").click() def test_hao(self): driver = self.driver driver.get(self.base_url + "/") driver.find_element(By.LINK_TEXT,"hao123").click() self.assertEqual(u"hao123_上网从这里开始", driver.title) #判断element是否存在,可删除 def is_element_present(self, how, what): try: self.driver.find_element(by=how, value=what) except NoSuchElementException as e: return False return True #判断alert是否存在,可删除 def is_alert_present(self): try: self.driver.switch_to_alert() except NoAlertPresentException as e: return False return True #关闭alert,可删除 def close_alert_and_get_its_text(self): try: alert = self.driver.switch_to_alert() alert_text = alert.text if self.accept_next_alert: alert.accept() else: alert.dismiss() return alert_text finally: self.accept_next_alert = True #test fixture,清除环境 def tearDown(self): self.driver.quit() self.assertEqual([], self.verificationErrors) if __name__ == "__main__": #执行用例 unittest.main()
自动化的测试中, 对于每个单独的case来说,一个case的执行结果中, 必然会有期望结果与实际结果, 来判断该case是通过还是失败, 在unittest 的库中提供了大量的实用方法来检查预期值与实际值, 来验证case的结果, 一般来说, 检查条件大体分为等价性, 逻辑比较以及其他, 如果给定的断言通过, 测试会继续执行到下一行的代码, 如果断言失败, 对应的case测试会立即停止或者生成错误信息( 一般打印错误信息即可) ,但是不要影响其他的case执行。
unittest 的单元测试库提供了标准的xUnit 断言方法。下面是一些常用的断言
断言方法 | 断言描述 |
---|---|
assertEqual(arg1, arg2, msg=None) | 验证arg1=arg2,不等则fail |
assertNotEqual(arg1, arg2, msg=None) | 验证arg1 != arg2, 相等则fail |
assertTrue(expr, msg=None) | 验证expr是true,如果为false,则fail |
assertFalse(expr,msg=None) | 验证expr是false,如果为true,则fail |
assertIs(arg1, arg2, msg=None) | 验证arg1、arg2是同一个对象,不是则fail |
assertIsNot(arg1, arg2, msg=None) | 验证arg1、arg2不是同一个对象,是则fail |
assertIsNone(expr, msg=None) | 验证expr是None,不是则fail |
assertIsNotNone(expr, msg=None) | 验证expr不是None,是则fail |
assertIn(arg1, arg2, msg=None) | 验证arg1是arg2的子串,不是则fail |
assertNotIn(arg1, arg2, msg=None) | 验证arg1不是arg2的子串,是则fail |
assertIsInstance(obj, cls, msg=None) | 验证obj是cls的实例,不是则fail |
assertNotIsInstance(obj, cls,msg=None) | 验证obj不是cls的实例,是则fail |
脚本执行完毕之后,还需要看到HTML报告,下面我们就通过HTMLTestRunner.py 来生成测试报告。
修改runall.py
import unittest,csv import os,sys import time import HTMLTestRunner #手工添加案例到套件, def createsuite(): discover=unittest.defaultTestLoader.discover('../testUnittest',pattern='test*.py',top_level_dir=None) print (discover) return discover if __name__=="__main__": curpath=sys.path[0] #取当前时间 now=time.strftime("%Y-%m-%d-%H %M %S",time.localtime(time.time())) if not os.path.exists(curpath+'/resultreport'): os.makedirs(curpath+'/resultreport') filename=curpath+'/resultreport/'+now+'resultreport.html' with open(filename,'wb') as fp: #出html报告 runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title=u'测试报告',description=u'用例执行情况',verbosity=2) suite=createsuite() runner.run(suite)
用例不可能每一次运行都成功,肯定运行时候有不成功的时候。如果可以捕捉到错误,并且把错误截图保存,这将是一个非常棒的功能,也会给我们错误定位带来方便。
例如编写一个函数,关键语句为driver.get_screenshot_as_file:
def savescreenshot(self,driver,file_name): if not os.path.exists('./image'): os.makedirs('./image') now=time.strftime("%Y%m%d-%H%M%S",time.localtime(time.time())) #截图保存 driver.get_screenshot_as_file('./image/'+now+'-'+file_name) time.sleep(1)
示例:testscreenshot.py
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import Select from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoAlertPresentException import unittest, time, re import os class Baidu1(unittest.TestCase): #test fixture,初始化环境 def setUp(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(30) self.base_url = "http://www.baidu.com/" self.verificationErrors = [] self.accept_next_alert = True #测试用例,必须以test开头 def test_hao(self): driver = self.driver driver.get(self.base_url + "/") driver.find_element(By.LINK_TEXT,"hao123").click() time.sleep(2) try: self.assertEqual(u'hao_上网从这里开始', driver.title) except: self.savescreenshot(driver,'hao.png') #判断element是否存在,可删除 def is_element_present(self, how, what): try: self.driver.find_element(by=how, value=what) except NoSuchElementException as e: return False return True #判断alert是否存在,可删除 def is_alert_present(self): try: self.driver.switch_to_alert() except NoAlertPresentException as e: return False return True #关闭alert,可删除 def close_alert_and_get_its_text(self): try: alert = self.driver.switch_to_alert() alert_text = alert.text if self.accept_next_alert: alert.accept() else: alert.dismiss() return alert_text finally: self.accept_next_alert = True #test fixture,清除环境 def tearDown(self): self.driver.quit() self.assertEqual([], self.verificationErrors) def savescreenshot(self,driver,file_name): if not os.path.exists('./image'): os.makedirs('./image') now=time.strftime("%Y%m%d-%H%M%S",time.localtime(time.time())) #截图保存 driver.get_screenshot_as_file('./image/'+now+'-'+file_name) time.sleep(1) if __name__ == "__main__": #执行用例 unittest.main() ''' 可以增加verbosity参数,例如unittest.main(verbosity=2) 在主函数中,直接调用main() ,在main中加入verbosity=2 ,这样测试的结果就会显示的更加详细。 这里的verbosity 是一个选项, 表示测试结果的信息复杂度,有三个值: 0 ( 静默模式): 你只能获得总的测试用例数和总的结果比如总共100个失败,20 成功80 1 ( 默认模式): 非常类似静默模式只是在每个成功的用例前面有个“ . ” 每个失败的用例前面有个“F” 2 ( 详细模式): 测试结果会显示每个测试用例的所有相关的信息 '''
之前我们的case都是数据和代码在一起编写。考虑如下场景:
需要多次执行一个案例,比如baidu搜索,分别输入中文、英文、数字等进行搜索,这时候需要编写3个案例吗?有没有版本一次运行?
python 的unittest 没有自带数据驱动功能。所以如果使用unittest,同时又想使用数据驱动,那么就可以使用DDT来完成。
ddt的安装:
pip install ddt
python setup.py install
dd.ddt:
装饰类,也就是继承自TestCase的类。
ddt.data:
装饰测试方法。参数是一系列的值。
data(value) 一次性传入一个参数data(value1,value2,…) 一次性传入多个参数,需要用@unpack映射data(*解析数据的方法(txt/csv文件))
ddt.file_data:
装饰测试方法。参数是文件名。文件可以是json 或者 yaml类型。
注意,如果文件以”.yml”或者”.yaml”结尾,ddt会作为yaml类型处理,其他所有文件都会作为json文件处理。
如果文件中是列表,每个列表的值会作为测试用例参数,同时作为测试用例方法名后缀显示。
如果文件中是字典,字典的key会作为测试用例方法的后缀显示,字典的值会作为测试用例参数。
ddt.unpack:
传递的是复杂的数据结构时使用。比如使用元组或者列表,添加unpack之后,ddt会自动把元组或者列表对应到多个参数上。字典也可以这样处理。
Testddt.py:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import Select from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoAlertPresentException import unittest, time, re import os,sys,csv from ddt import ddt, data, unpack ,file_data def getCsv(file_name): rows=[] path=sys.path[0].replace('\testddt','') print (path) with open(path+'/data/'+file_name,'r+b') as f: readers=csv.reader(f,delimiter=',',quotechar='|') next(readers,None) for row in readers: temprows=[] for i in row: temprows.append(i.decode('gbk')) rows.append(temprows) return rows #引入ddt @ddt class Testddt(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(30) self.base_url = "http://www.baidu.com" self.verificationErrors = [] self.accept_next_alert = True #测试用例,必须以test开头 #增加ddt数据 @data('selenium','unittest','Junit') #@data(2,3,4) #单变更时不使用unpack #@data([3, 2], [4, 3], [5, 3]) # @data(*getCsv('test_baidu_data.csv')) #使用file_data需要在cmd窗口下运行,否则找不到文件 #@file_data('test_data_list.json') # @unpack def test_hao(self,value,expected_value): # def test_hao(self,value): driver = self.driver driver.get(self.base_url + "/") driver.find_element(By.ID,"kw").clear() driver.find_element(By.ID,"kw").send_keys(value) driver.find_element(By.ID,"su").click() time.sleep(2) self.assertEqual(expected_value, driver.title) print (expected_value) print (driver.title) #判断element是否存在,可删除 def is_element_present(self, how, what): try: self.driver.find_element(by=how, value=what) except NoSuchElementException as e: return False return True #判断alert是否存在,可删除 def is_alert_present(self): try: self.driver.switch_to_alert() except NoAlertPresentException as e: return False return True #关闭alert,可删除 def close_alert_and_get_its_text(self): try: alert = self.driver.switch_to_alert() alert_text = alert.text if self.accept_next_alert: alert.accept() else: alert.dismiss() return alert_text finally: self.accept_next_alert = True #test fixture,清除环境 def tearDown(self): self.driver.quit() self.assertEqual([], self.verificationErrors) def savescreenshot(self,driver,file_name): if not os.path.exists('./image'): os.makedirs('./image') now=time.strftime("%Y%m%d-%H%M%S",time.localtime(time.time())) #截图保存 driver.get_screenshot_as_file('./image/'+now+'-'+file_name) time.sleep(1) if __name__ == "__main__": #执行用例 unittest.main()
以上がPython自動テストフレームワークであるunittestの使い方は?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。