1.1 unittest簡介
前言
(python基礎比較弱的,建議大家多花點時間把基礎語法學好,這里有套視頻,可以照着練習下:http://pan.baidu.com/s/1i44jZdb 密碼:92fs)
熟悉java的應該都清楚常見的單元測試框架Junit和TestNG,這個招聘的需求上也是經常見到的。python里面也有單元測試框架-unittest,相當於是一個python版的junit。
python里面的單元測試框架除了unittest,還有一個pytest框架,這個用的比較少,后面有空再繼續分享。
1.1.1 unittest簡介
1).先導入unittest
2).用help函數查看源碼解析
3).查看描述:
Python unit testing framework, based on Erich Gamma's JUnit and KentBeck's Smalltalk testing framework.
翻譯:python的單元測試框架,是基於java的junit測試框架。
1.1.2 簡單用法
1).可以把上圖的這段代碼copy出來,單獨運行,看看測試結果。
Simple usage:
import unittest class IntegerArithmeticTestCase(unittest.TestCase): deftestAdd(self): ## test method names begin 'test*' self.assertEqual((1 + 2), 3) self.assertEqual(0 + 1, 1) deftestMultiply(self): self.assertEqual((0 * 10), 0) self.assertEqual((5 * 8), 40) if __name__ == '__main__': unittest.main()
2).第一行是導入unittest這個模塊
3).class這一行是定義一個測試的類,並繼承unittest.TestCase這個類
4).接下來是定義了兩個測試case名稱:testAdd和testMultiply
5).注釋里面有句話很重要,這個要敲下黑板記筆記了:## test method names begin 'test*'
--翻譯:測試用例的名稱要以test開頭
6).然后是斷言assert,這里的斷言方法是assertEqual-判斷兩個是否相等,
這個斷言可以是一個也可以是多個
7).if下面的這個unittest.main()是運行主函數,運行后會看到測試結果(跑了兩個用例耗時0.000秒,兩個用例都通過):
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
1.1.3 小試牛刀
1).上面的兩個案例是加法和乘法,我們可以寫個case試下減法和除法。
2).有很多小伙伴不知道斷言怎么寫,斷言其實就是拿實際結果和期望結果去對比,對比的方法很多,這里只是舉的最簡單的一個判斷相等的方法。
3).最后運行結果,第二個是失敗的,失敗原因:AssertionError: 3 != 3.5
F.
======================================================================
FAIL: testDivide (__main__.Test)
這里是測試除法
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:/test/web-project/p.py", line 14, in testDivide
self.assertEqual(result, hope)
AssertionError: 3 != 3.5
----------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=1)
1.1.4 前置和后置
1).setUp:在寫測試用例的時候,每次操作其實都是基於打開瀏覽器輸入對應網址這些操作,這個就是執行用例的前置條件。
2).tearDown:執行完用例后,為了不影響下一次用例的執行,一般有個數據還原的過程,這就是執行用例的后置條件。
3).很多人執行完用例,都不去做數據還原,以致於下一個用例執行失敗,這就是不喜歡擦屁股的事情,習慣不好。
4).前置和后置都是非必要的條件,如果沒有也可以寫pass
1.1.5 博客案例
1).打開博客首頁為例,寫一個簡單的case
2).判斷title完全等於期望結果
3).運行通過,下面會有一個綠條顯示:1 test passed
1.1.6 參考代碼
# coding=utf-8 from selenium import webdriver from selenium.webdriver.support import expected_conditions as EC import time import unittest class Blog(unittest.TestCase): def setUp(self): self.driver = webdriver.Firefox() self.driver.get("http://www.cnblogs.com/yoyoketang") def test_blog(self): time.sleep(3) result = EC.title_is(u'上海-悠悠 - 博客園')(self.driver) print result self.assertTrue(result) def tearDown(self): self.driver.quit() if __name__ == "__main__": unittest.main()
1.2 unittest執行順序
前言
很多初學者在使用unittest框架時候,不清楚用例的執行順序到底是怎樣的。對測試類里面的類和方法分不清楚,不知道什么時候執行,什么時候不執行。
本篇通過最簡單案例詳細講解unittest執行順序。
一、案例分析
1.先定義一個測試類,里面寫幾個簡單的case
# coding:utf-8 import unittest import time class Test(unittest.TestCase): def setUp(self): print "start!" def tearDown(self): time.sleep(1) print "end!" def test01(self): print "執行測試用例01" def test03(self): print "執行測試用例03" def test02(self): print "執行測試用例02" def addtest(self): print "add方法" if __name__ == "__main__": unittest.main()
二、執行結果
D:\test\python2\python.exe D:/test/test01.py
start!
執行測試用例01
end!
start!
執行測試用例02
end!
start!
執行測試用例03
end!
.
----------------------------------------------------------------------
Ran 3 tests in 3.001s
OK
三、結果分析
1.執行順序:
start!-執行測試用例01-end!
start!-執行測試用例02-end!
start!-執行測試用例03-end!
2.從執行結果可以看出幾點:
--先執行的前置setUp,然后執行的用例(test*),最后執行的后置tearDown。
--測試用例(test*)的執行順序是根據01-02-03執行的,也就是說根據用例名稱來順序執行的。
--addtest(self)這個方法沒執行,說明只執行test開頭的用例。
四、selenium實例
1.具體實例參考 登錄方法(參數化)
# coding:utf-8 from selenium import webdriver import unittest import time class Bolg(unittest.TestCase): u'''登錄博客''' def setUp(self): self.driver = webdriver.Firefox() url = "https://passport.cnblogs.com/user/signin" self.driver.get(url) self.driver.implicitly_wait(30) def login(self, username, psw): u'''這里寫了一個登錄的方法,賬號和密碼參數化''' self.driver.find_element_by_id("input1").send_keys(username) self.driver.find_element_by_id("input2").send_keys(psw) self.driver.find_element_by_id("signin").click() time.sleep(3) def is_login_sucess(self): u'''判斷是否獲取到登錄賬戶名稱''' try: text = self.driver.find_element_by_id("lnk_current_user").text print text return True except: return False def test_01(self): u'''登錄案例參考:賬號,密碼自己設置''' self.login(u"上海-悠悠", u"xxxx") # 調用登錄方法 # 判斷結果 result = self.is_login_sucess() self.assertTrue(result) def test_02(self): u'''登錄案例參考:賬號,密碼自己設置''' self.login(u"上海-悠悠", u"xxxx") # 調用登錄方法 # 判斷結果 result = self.is_login_sucess() self.assertTrue(result) def tearDown(self): self.driver.quit() if __name__ == "__main__": unittest.main()
1.3 unittest批量執行
我們在寫用例的時候,單個腳本的用例好執行,那么多個腳本的時候,如何批量執行呢?這時候就需要用到unittet里面的discover方法來加載用例了。
加載用例后,用unittest里面的TextTestRunner這里類的run方法去一次執行多個腳本的用例。
一、新建測試項目
1.pycharm左上角File>New Projetc>Pure Python,在location位置命名一個測試工程的名稱:yoyotest,然后保存
2.選中剛才新建的工程右鍵>New>Python Package>新建一個case文件夾
3.重復第2步的操作,新建一個case的文件夾,在里面添加一個baidu和一個blog的文件夾,里面分別有兩個用例的腳本,如下圖所示。
test_01,test_02,test_03,test_04是我們寫用例的腳本
4.test_01創建完后,打開腳本,寫入用例
5.在yoyotest這個項目下面創建一個腳本run_all_case.py,接下來用這個腳本去批量執行所有的用例。
二、diascover加載測試用例
1.discover方法里面有三個參數:
-case_dir:這個是待執行用例的目錄。
-pattern:這個是匹配腳本名稱的規則,test*.py意思是匹配test開頭的所有腳本。
-top_level_dir:這個是頂層目錄的名稱,一般默認等於None就行了。
2.discover加載到的用例是一個list集合,需要重新寫入到一個list對象testcase里,這樣就可以用unittest里面的TextTestRunner這里類的run方法去執行。
3.運行后結果如下,就是加載到的所有測試用例了:
<unittest.suite.TestSuite tests=[<baidu.test_01.Test testMethod=test01>, <baidu.test_01.Test testMethod=test02>, <baidu.test_01.Test testMethod=test03>, <baidu.test_02.Test
testMethod=test01>, <baidu.test_02.Test testMethod=test02>, <baidu.test_02.Test testMethod=test03>, <bolg.test_03.Test testMethod=test01>, <bolg.test_03.Test testMethod=test02>,
<bolg.test_03.Test testMethod=test03>, <bolg.test_04.Test testMethod=test01>, <bolg.test_04.Test testMethod=test02>, <bolg.test_04.Test testMethod=test03>]>
三、run測試用例
1.為了更方便的理解,可以把上面discover加載用例的方法封裝下,寫成一個函數
2.參考代碼:
# coding:utf-8 import unittest import os # 用例路徑 case_path = os.path.join(os.getcwd(), "case") # 報告存放路徑 report_path = os.path.join(os.getcwd(), "report") def all_case(): discover = unittest.defaultTestLoader.discover(case_path, pattern="test*.py", top_level_dir=None) print(discover) return discover if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(all_case())
1.4 unittest之裝飾器(@classmethod)
前言
前面講到unittest里面setUp可以在每次執行用例前執行,這樣有效的減少了代碼量,但是有個弊端,比如打開瀏覽器操作,每次執行用例時候都會重新打開,這樣就會浪費很多時間。
於是就想是不是可以只打開一次瀏覽器,執行完用例再關閉呢?這就需要用到裝飾器(@classmethod)來解決了。
一、裝飾器
1.用setUp與setUpClass區別
setup():每個測試case運行前運行
teardown():每個測試case運行完后執行
setUpClass():必須使用@classmethod 裝飾器,所有case運行前只運行一次
tearDownClass():必須使用@classmethod裝飾器,所有case運行完后只運行一次
2.@是修飾符,classmethod是python里的類方法
二、執行順序
1.用類方法寫幾個簡單case,可以對比這篇:Selenium2+python自動化52-unittest執行順序
# coding:utf-8 import unittest import time
class Test(unittest.TestCase): @classmethod def setUpClass(cls): print "start!" @classmethod def tearDownClass(cls): time.sleep(1) print "end!" def test01(self): print "執行測試用例01" def test03(self): print "執行測試用例03" def test02(self): print "執行測試用例02" def addtest(self): print "add方法"
if __name__ == "__main__": unittest.main()
2.從執行結果可以看出,前置和后置在執行用例前只執行了一次。
start!
執行測試用例01
執行測試用例02
執行測試用例03
...end!
----------------------------------------------------------------------
Ran 3 tests in 1.001s
三、selenium實例
1.可以把打開瀏覽器操作放到前置setUpClass(cls)里,這樣就可以實現打開一次瀏覽器,執行多個case了
# coding:utf-8 from selenium import webdriver from selenium.webdriver.support import expected_conditions as EC import unittest
class BolgHome(unittest.TestCase):
u'''博客首頁''' @classmethod def setUpClass(cls): cls.driver = webdriver.Firefox() url = "http://www.cnblogs.com/yoyoketang/" cls.driver.get(url) cls.driver.implicitly_wait(30)
@classmethod def tearDownClass(cls): cls.driver.quit()
def test_01(self): u'''驗證元素存在:博客園''' locator = ("id", "blog_nav_sitehome") text = u"博客園" result = EC.text_to_be_present_in_element(locator, text)(self.driver) self.assertTrue(result) def test_02(self): u'''驗證元素存在:首頁''' locator = ("id", "blog_nav_myhome") text = u"首頁" result = EC.text_to_be_present_in_element(locator, text)(self.driver) self.assertTrue(result)
if __name__ == "__main__": unittest.main()
1.5 unittest生成測試報告HTMLTestRunner
前言
批量執行完用例后,生成的測試報告是文本形式的,不夠直觀,為了更好的展示測試報告,最好是生成HTML格式的。
unittest里面是不能生成html格式報告的,需要導入一個第三方的模塊:HTMLTestRunner
備注:(以下是python2.7的HTMLTestRunner,python3.x的HTMLTestRunner需要自己稍做修改,可以在這里下載:http://pan.baidu.com/s/1hs5OXNY)
一、導入HTMLTestRunner
1.這個模塊下載不能通過pip安裝了,只能下載后手動導入,下載地址:http://tungwaiyip.info/software/HTMLTestRunner.html
2.Download下HTMLTestRunner.py文件就是我們需要下載的包。
3.下載后手動拖到python安裝文件的Lib目錄下
二、demo解析
1.下載Download下的第二個文件test_HTMLTestRunner.py,這個就是官方給的一個測試demo了,從這個文件可以找到該模塊的用法。
2.找到下圖這段,就是官方給的一個demo了,test_main()里上半部分就是加載測試case,我們不需要搞這么復雜。
參考前面一篇內容就行了Selenium2+python自動化53-unittest批量執行(discover)
3.最核心的代碼是下面的紅色區域,這個就是本篇的重點啦。
三、生成html報告
1.我們只需把上面紅色區域代碼copy到上一篇的基礎上稍做修改就可以了,這里主要有三個參數:
--stream:測試報告寫入文件的存儲區域
--title:測試報告的主題
--description:測試報告的描述
2.report_path是存放測試報告的地址
四、測試報告詳情
1.找到測試報告文件,用瀏覽器打開,點開View里的Detail可以查看詳情描述。
2.為了生成帶中文描述的測試用例,可以在case中添加注釋,如在test_01的腳本添加如下注釋:
class Test(unittest.TestCase): def setUp(self): print "start!" def tearDown(self): time.sleep(1) print "end!" def test01(self): u'''測試登錄用例,賬號:xx 密碼xx''' print "執行測試用例01" def test03(self): u'''測試登搜索用例,關鍵詞:xxx''' print "執行測試用例03"
3.重新運行后查看測試報告
五、參考代碼:
1.我下面的代碼文件路徑用的相對路徑,這樣就避免代碼換個地址找不到路徑的情況了
# coding:utf-8 import unittest import os import HTMLTestRunner # 用例路徑 case_path = os.path.join(os.getcwd(), "case") # 報告存放路徑 report_path = os.path.join(os.getcwd(), "report") def all_case(): discover = unittest.defaultTestLoader.discover(case_path, pattern="test*.py", top_level_dir=None) print(discover) return discover if __name__ == "__main__": # runner = unittest.TextTestRunner() # runner.run(all_case()) # html報告文件路徑 report_abspath = os.path.join(report_path, "result.html") fp = open(report_abspath, "wb") runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u'自動化測試報告,測試結果如下:', description=u'用例執行情況:') # 調用add_case函數返回值 runner.run(all_case()) fp.close()
1.6 html報告亂碼問題優化
前言
python2用HTMLTestRunner生成測試報告時,有中文輸出情況會出現亂碼,這個主要是編碼格式不統一,改下編碼格式就行。
下載地址:http://tungwaiyip.info/software/HTMLTestRunner.html
一、中文亂碼
1.測試報告中,msg自定義異常內容有中文情況會出現亂碼,如下圖所示
二、修改編碼
1.找到HTMLTestRunner.py文件,搜索:uo =
2.找到紅色區域設置編碼的兩個地方
3.注釋掉紅色區域這兩個設置,重新添加編碼格式為:uo = o.decode('utf-8') ue = e.decode('utf-8')
4.修改好之后記得保存,重新運行,亂碼問題就解決了
三、python3報告問題
1.python3的小伙伴直接用這個下載地址:http://tungwaiyip.info/software/HTMLTestRunner.html的文件,是不能直接生成報告的,需要稍做修改
2.修改后的源文件已經打包:https://files.cnblogs.com/files/zidonghua/HTMLTestRunner%28%E7%8B%AC%E5%AE%B6%E5%90%88%E9%9B%86%29.zip
(另外很多朋友在簡單的示例代碼中,生成不了測試報告,我來上傳一份完整項目模板:https://files.cnblogs.com/files/zidonghua/%E6%B5%8B%E8%AF%95%E6%8A%A5%E5%91%8A%E6%90%9E%E4%B8%8D%E5%87%BA%E6%9D%A5%E7%9A%84%E7%9C%8B%E8%BF%99%E9%87%8C.rar)
1.7 unittest之斷言
前言
在測試用例中,執行完測試用例后,最后一步是判斷測試結果是pass還是fail,自動化測試腳本里面一般把這種生成測試結果的方法稱為斷言(assert)。
用unittest組件測試用例的時候,斷言的方法還是很多的,下面介紹幾種常用的斷言方法:assertEqual、assertIn、assertTrue
1.7.1 簡單案例
1).下面寫了4個case,其中第四個是執行失敗的
# coding:utf-8 import unittest class Test(unittest.TestCase): def test01(self): '''判斷 a == b ''' a = 1 b = 1 self.assertEqual(a, b) def test02(self): '''判斷 a in b''' a = "hello" b = "hello world!" self.assertIn(a, b) def test03(self): '''判斷 a isTrue ''' a = True self.assertTrue(a) def test04(self): '''失敗案例''' a = "上海-悠悠" b = "yoyo" self.assertEqual(a, b) if __name__ == "__main__": unittest.main()
2).執行結果如下
Failure
Expected :'\xe4\xb8\x8a\xe6\xb5\xb7-\xe6\x82\xa0\xe6\x82\xa0'
Actual :'yoyo'
<Click to see difference>
Traceback (most recent call last):
File "D:\test\yoyotest\kecheng\test12.py", line 27, in test04
self.assertEqual(a, b)
AssertionError: '\xe4\xb8\x8a\xe6\xb5\xb7-\xe6\x82\xa0\xe6\x82\xa0' != 'yoyo'
3.執行的結果,中文編碼不對,沒正常顯示中文,遇到這種情況,可以自定義異常輸出
1.7.2 自定義異常
1).以assertEqual為例分析:
assertEqual(self, first, second, msg=None)
Fail if the two objects are unequal as determined by the'=='
operator.
2).翻譯:如果兩個對象不能相等,就返回失敗,相當於return: first==second
3).這里除了相比較的兩個參數first和second,還有第三個參數msg=None,這個msg參數就是遇到異常后自定義輸出信息
1.7.3 unittest常用的斷言方法
1).assertEqual(self, first, second,msg=None)
--判斷兩個參數相等:first == second
2).assertNotEqual(self, first, second,msg=None)
--判斷兩個參數不相等:first != second
3).assertIn(self, member, container,msg=None)
--判斷是字符串是否包含:member in container
4).assertNotIn(self, member,container, msg=None)
--判斷是字符串是否不包含:member not in container
5).assertTrue(self, expr, msg=None)
--判斷是否為真:expr is True
6).assertFalse(self, expr, msg=None)
--判斷是否為假:expr is False
7).assertIsNone(self, obj, msg=None)
--判斷是否為None:objis None
8).assertIsNotNone(self, obj,msg=None)
--判斷是否不為None:obj is not None
3.7.4 unittest所有斷言方法
1).下面是unittest框架支持的所有斷言方法,有興趣的同學可以慢慢看。(官方資料)
| assertAlmostEqual(self, first, second, places=None, msg=None,delta=None) | Fail if the two objects are unequal asdetermined by their | difference rounded to the given number ofdecimal places | (default 7) and comparing to zero, or bycomparing that the | between the two objects is more than the givendelta. | | Note that decimal places (from zero) areusually not the same | as significant digits (measured from the mostsignficant digit). | | If the two objects compare equal then they willautomatically | compare almost equal. | | assertAlmostEquals = assertAlmostEqual(self, first, second,places=None, msg=None, delta=None) | | assertDictContainsSubset(self, expected, actual, msg=None) | Checks whether actual is a superset ofexpected. | | assertDictEqual(self, d1, d2, msg=None) | | assertEqual(self, first, second, msg=None) | Fail if the two objects are unequal asdetermined by the '==' | operator. | | assertEquals = assertEqual(self, first, second, msg=None) | | assertFalse(self, expr, msg=None) | Check that the expression is false. | | assertGreater(self, a, b, msg=None) | Just like self.assertTrue(a > b), but with anicer default message. | | assertGreaterEqual(self, a, b, msg=None) | Just like self.assertTrue(a >= b), but witha nicer default message. | | assertIn(self, member, container, msg=None) | Just like self.assertTrue(a in b), but with anicer default message. | | assertIs(self, expr1, expr2, msg=None) | Just like self.assertTrue(a is b), but with anicer default message. | | assertIsInstance(self, obj, cls, msg=None) | Same as self.assertTrue(isinstance(obj, cls)),with a nicer | default message. | | assertIsNone(self, obj, msg=None) | Same as self.assertTrue(obj is None), with anicer default message. | | assertIsNot(self, expr1, expr2, msg=None) | Just like self.assertTrue(a is not b), but witha nicer default message. | | assertIsNotNone(self, obj, msg=None) | Included for symmetry with assertIsNone. | | assertItemsEqual(self, expected_seq, actual_seq, msg=None) | An unordered sequence specific comparison. Itasserts that | actual_seq and expected_seq have the sameelement counts. | Equivalent to:: | | self.assertEqual(Counter(iter(actual_seq)), | Counter(iter(expected_seq))) | | Asserts that each element has the same count inboth sequences. | Example: | - [0, 1, 1] and [1, 0,1] compare equal. | - [0, 0, 1] and [0, 1]compare unequal. | | assertLess(self, a, b, msg=None) | Just like self.assertTrue(a < b), but with anicer default message. | | assertLessEqual(self, a, b, msg=None) | Just like self.assertTrue(a <= b), but witha nicer default message. | | assertListEqual(self, list1, list2, msg=None) | A list-specific equality assertion. | | Args: | list1: The first listto compare. | list2: The second listto compare. | msg: Optional messageto use on failure instead of a list of | differences. | | assertMultiLineEqual(self, first, second, msg=None) | Assert that two multi-line strings are equal. | | assertNotAlmostEqual(self, first, second, places=None, msg=None,delta=None) | Fail if the two objects are equal as determinedby their | difference rounded to the given number ofdecimal places | (default 7) and comparing to zero, or bycomparing that the | between the two objects is less than the givendelta. | | Note that decimal places (from zero) areusually not the same | as significant digits (measured from the mostsignficant digit). | | Objects that are equal automatically fail. | | assertNotAlmostEquals = assertNotAlmostEqual(self, first, second, places=None,msg=None, delta=None) | | assertNotEqual(self, first, second, msg=None) | Fail if the two objects are equal as determinedby the '!=' | operator. | | assertNotEquals = assertNotEqual(self, first, second, msg=None) | | assertNotIn(self, member, container, msg=None) | Just like self.assertTrue(a not in b), but witha nicer default message. | | assertNotIsInstance(self, obj, cls, msg=None) | Included for symmetry with assertIsInstance. | | assertNotRegexpMatches(self, text, unexpected_regexp, msg=None) | Fail the test if the text matches the regularexpression. | | assertRaises(self, excClass, callableObj=None, *args, **kwargs) | Fail unless an exception of class excClass israised | by callableObj when invoked with arguments argsand keyword | arguments kwargs. If a different type ofexception is | raised, it will not be caught, and the testcase will be | deemed to have suffered an error, exactly asfor an | unexpected exception. | | If called with callableObj omitted or None,will return a | context object used like this:: | | withself.assertRaises(SomeException): | do_something() | | The context manager keeps a reference to theexception as | the 'exception' attribute. This allows you toinspect the | exception after the assertion:: | | withself.assertRaises(SomeException) as cm: | do_something() | the_exception =cm.exception | self.assertEqual(the_exception.error_code, 3) | | assertRaisesRegexp(self, expected_exception, expected_regexp,callable_obj=None, *args, **kwargs) | Asserts that the message in a raised exceptionmatches a regexp. | | Args: | expected_exception:Exception class expected to be raised. | expected_regexp: Regexp(re pattern object or string) expected | to be found in error message. | callable_obj: Functionto be called. | args: Extra args. | kwargs: Extra kwargs. | | assertRegexpMatches(self, text, expected_regexp, msg=None) | Fail the test unless the text matches theregular expression. | | assertSequenceEqual(self, seq1, seq2, msg=None, seq_type=None) | An equality assertion for ordered sequences(like lists and tuples). | | For the purposes of this function, a validordered sequence type is one one | which can be indexed, has a length, and has anequality operator. | | Args: | seq1: The firstsequence to compare. | seq2: The secondsequence to compare. | seq_type: The expecteddatatype of the sequences, or None if no | datatype should be enforced. | msg: Optional messageto use on failure instead of a list of | differences. | | assertSetEqual(self, set1, set2, msg=None) | A set-specific equality assertion. | | Args: | set1: The first set tocompare. | set2: The second set tocompare. | msg: Optional messageto use on failure instead of a list of | differences. | | assertSetEqual uses ducktyping to supportdifferent types of sets, and | is optimized for sets specifically (parametersmust support a | difference method). | | assertTrue(self, expr, msg=None) | Check that the expression is true. | | assertTupleEqual(self, tuple1, tuple2, msg=None) | A tuple-specific equality assertion. | | Args: | tuple1: The first tupleto compare. | tuple2: The secondtuple to compare. | msg: Optional messageto use on failure instead of a list of | differences.
1.8 搭建簡易項目
前言
到unittest這里基本上可以搭建一個簡易的項目框架了,我們可以用一條run_main.py腳本去控制執行所有的用例,並生成報告,發送郵件一系列的動作
一、新建工程
1.打開pycharm左上角File>New Project,在Location位置輸入項目名稱:D:\test\test_blog
2.創建之后,選擇Opin in current window就可以了
二、項目結構
1.在測試工程下,創建文件夾,一定要選Python Package的方式創建,要不然后面導入自己寫的模塊會出現各種問題
2.在工程下創建以下幾個文件
--test_case 這個文件夾放所有測試用例
----blog_home 可以按功能用例模塊划分
---------test_home
---------test_home_1 測試用例以test開頭命名
----blog_login
---------test_login
----blog_set
---------test_set
--test_report
--run_main.py 注意這個腳本放文件根目錄
三、run_main
1.run_main.py這個腳本里面寫主函數,控制執行所有的用例,最終我們只需要運行這個腳本就可以了
2.我們也可以在cmd里執行這個腳本文件,這樣就不用依賴pycharm去執行了(后續用jenkins執行,也是同樣道理,啟動cmd執行腳本)
>>d:
>>cd test\test_blog
>>python run_main.py
3.run_main.py源代碼在下一章節。
1.8.1 生成報告的源碼下載(兼容python2和3)
生成測試項目報告模板:
https://files.cnblogs.com/files/zidonghua/%E7%94%9F%E6%88%90%E6%B5%8B%E8%AF%95%E6%8A%A5%E5%91%8A%E9%A1%B9%E7%9B%AE%E6%A8%A1%E6%9D%BF.zip
測試報告搞不出來的看這里:
https://files.cnblogs.com/files/zidonghua/%E6%B5%8B%E8%AF%95%E6%8A%A5%E5%91%8A%E6%90%9E%E4%B8%8D%E5%87%BA%E6%9D%A5%E7%9A%84%E7%9C%8B%E8%BF%99%E9%87%8C2.rar
1.9 run_main.py源碼(兼容python2和3)
以下代碼在python2和python3上都跑通過,python3只需注釋掉上面紅色框框區域代碼就行(最后一步發送郵箱代碼,我注釋掉了)。
# coding=utf-8 import unittest import time import HTMLTestRunner from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart import smtplib import os ####下面三行代碼python2報告出現亂碼時候可以加上#### import sys reload(sys) sys.setdefaultencoding('utf8')
# 這個是優化版執行所有用例並發送報告,分四個步驟 # 第一步加載用例 # 第二步執行用例 # 第三步獲取最新測試報告 # 第四步發送郵箱 (這一步不想執行的話,可以注釋掉最后面那個函數就行)
def add_case(case_path, rule): '''加載所有的測試用例''' testunit = unittest.TestSuite() # 定義discover方法的參數 discover = unittest.defaultTestLoader.discover(case_path, pattern=rule, top_level_dir=None) # discover方法篩選出來的用例,循環添加到測試套件中 # for test_suite in discover: # for test_case in test_suite: # testunit.addTests(test_case) # print testunit testunit.addTests(discover) # 直接加載discover print(testunit) return testunit
def run_case(all_case, report_path): '''執行所有的用例, 並把結果寫入測試報告''' now = time.strftime("%Y_%m_%d %H_%M_%S") report_abspath = os.path.join(report_path, now+"result.html") # report_abspath = "D:\\web_project\\report\\"+now+"result.html" fp = open(report_abspath, "wb") runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u'自動化測試報告,測試結果如下:', description=u'用例執行情況:') # 調用add_case函數返回值 runner.run(all_case) fp.close()
def get_report_file(report_path): '''獲取最新的測試報告''' lists = os.listdir(report_path) lists.sort(key=lambda fn: os.path.getmtime(os.path.join(report_path, fn))) print (u'最新測試生成的報告: '+lists[-1]) # 找到最新生成的報告文件 report_file = os.path.join(report_path, lists[-1]) return report_file
def send_mail(sender, psw, receiver, smtpserver, report_file): '''發送最新的測試報告內容''' # 讀取測試報告的內容 with open(report_file, "rb") as f: mail_body = f.read() # 定義郵件內容 msg = MIMEMultipart() body = MIMEText(mail_body, _subtype='html', _charset='utf-8') msg['Subject'] = u"自動化測試報告" msg["from"] = sender msg["to"] = psw # 加上時間戳 # msg["date"] = time.strftime('%a, %d %b %Y %H_%M_%S %z') msg.attach(body) # 添加附件 att = MIMEText(open(report_file, "rb").read(), "base64", "utf-8") att["Content-Type"] = "application/octet-stream" att["Content-Disposition"] = 'attachment; filename= "report.html"' msg.attach(att) # 登錄郵箱 smtp = smtplib.SMTP() # 連接郵箱服務器 smtp.connect(smtpserver) # 用戶名密碼 smtp.login(sender, psw) smtp.sendmail(sender, receiver, msg.as_string()) smtp.quit() print('test report email has send out !')
if __name__ == "__main__": # 測試用例的路徑、匹配規則 case_path = "D:\\test\\newp\\case" rule = "test*.py" all_case = add_case(case_path, rule) # 1加載用例 # 生成測試報告的路徑 report_path = "D:\\test\\newp\\report" run_case(all_case, report_path) # 2執行用例 # 獲取最新的測試報告文件 report_file = get_report_file(report_path) # 3獲取最新的測試報告 #郵箱配置 sender = "yoyo@xxx.com" psw = "xxx" # 收件人多個時,中間用逗號隔開,如'a@xx.com,b@xx.com' receiver = "yoyo@xxx.com" smtp_server = 'smtp.xxx.com' # send_mail(sender, psw, receiver, smtp_server, report_file) # 4最后一步發送報告,需要發郵件就取消注釋。
1.10 練習題1:模塊導入(登錄方法)
以登錄博客園為案例https://passport.cnblogs.com/user/signin
一、登錄方法封裝
1.我們可以把登錄寫成一個登錄類,里面寫個登錄的方法,保存文件為login_pub.py
# coding:utf-8 ''' 這里寫了一個登錄博客園的類,登錄博客園方法 ''' class Login_Blog(): '''登錄類封裝''' def __init__(self, driver): '''初始化driver參數''' self.driver = driver def input_user(self, username): '''輸入用戶名''' self.driver.find_element_by_id("input1").clear() self.driver.find_element_by_id("input1").send_keys(username) def input_psw(self,psw): '''輸入密碼''' self.driver.find_element_by_id("input2").clear() self.driver.find_element_by_id("input2").send_keys(psw) def click_button(self): '''點擊登錄按鈕''' self.driver.find_element_by_id("signin").click() def login(self, username, psw): '''登錄公共方法''' self.input_user(username) self.input_psw(psw) self.click_button()
2.調用登錄公共方法
# coding:utf-8 from selenium import webdriver import unittest from login_pub import Login_Blog login_url = "https://passport.cnblogs.com/user/signin"
class TetsLogin(unittest.TestCase): def setUp(self): self.driver = webdriver.Firefox() self.driver.get(login_url) def tearDown(self): self.driver.quit() def test_login(self): # 調用登錄類里面的login方法 Login_Blog(self.driver).login("xxx", "111") self.driver.find_element() # 后面接着的操作省略了
if __name__ == "__main__": unittest.main()
1.11 練習題2:捕獲異常
前言
在定位元素的時候,經常會遇到各種異常,為什么會發生這些異常,遇到異常又該如何處理呢?
本篇通過學習selenium的exceptions模塊,了解異常發生的原因。
一、發生異常
1.打開博客首頁,定位“新隨筆”元素,此元素
2.為了故意讓它定位失敗,我在元素屬性后面加上xx
3.運行失敗后如下圖所示,程序在查找元素的這一行發生了中斷,不會繼續執行click事件了
二、捕獲異常
1.為了讓程序繼續執行,我們可以用try...except...捕獲異常。捕獲異常后可以打印出異常原因,這樣以便於分析異常原因。
2.從如下異常內容可以看出,發生異常原因是:NoSuchElementException
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: {"method":"id","selector":"blog_nav_newpostxx"}
3.從selenium.common.exceptions 導入 NoSuchElementException類。
三、參考代碼:
# coding:utf-8 from selenium import webdriver from selenium.common.exceptions import NoSuchElementException
driver = webdriver.Firefox() driver.get("http://www.cnblogs.com/yoyoketang/")
# 定位首頁"新隨筆" try: element = driver.find_element("id", "blog_nav_newpostxx") except NoSuchElementException as msg: print u"查找元素異常%s"%msg # 點擊該元素 else: element.click()
四、selenium常見異常
1.NoSuchElementException:沒有找到元素
2.NoSuchFrameException:沒有找到iframe
3.NoSuchWindowException:沒找到窗口句柄handle
4.NoSuchAttributeException:屬性錯誤
5.NoAlertPresentException:沒找到alert彈出框
6.ElmentNotVisibleException:元素不可見
7.ElementNotSelectableException:元素沒有被選中
8.TimeoutException:查找元素超時
備注:其它異常與源碼在Lib目錄下:selenium/common/exceptions有興趣的可以看看。
1.12 練習題3:異常后截圖
前言
在執行用例過程中由於是無人值守的,用例運行報錯的時候,我們希望能對當前屏幕截圖,留下證據。
在寫用例的時候,最后一步是斷言,可以把截圖的動作放在斷言這里,那么如何在斷言失敗后截圖呢?
一、截圖方法
1.get_screenshot_as_file(self, filename)
--這個方法是獲取當前window的截圖,出現IOError時候返回False,截圖成功返回True。
filename參數是保存文件的路徑。
Usage:
driver.get_screenshot_as_file('/Screenshots/foo.png')
2.get_screenshot_as_base64(self)
--這個方法也是獲取屏幕截圖,保存的是base64的編碼格式,在HTML界面輸出截圖的時候,會用到。
比如,想把截圖放到html測試報告里。
Usage:
driver.get_screenshot_as_base64()
3.get_screenshot_as_png(self)
--這個是獲取屏幕截圖,保存的是二進制數據,很少用到。
Usage:
driver.get_screenshot_as_png()
二、異常后截圖
1.為了能拋異常,把定位登錄按鈕的id換了個錯的id。
2.給圖片命名時候加個時間戳,避免同一個文件名稱被覆蓋掉。
3.文件路徑,這里直接寫的文件名稱,就是跟當前的腳本同一個路徑。如果圖片輸出到其它文件路徑,需要些文件的絕對路徑了。
4.截圖的結果,如果沒截到圖返回False,截圖成功會返回True。
三、selenium實例
1.在unittest框架里寫用例的時候,我們希望在斷言失敗的時候,對當前屏幕截圖。
2.如果加try...except捕獲異常后結果,此時所有的測試用例都是通過的了,會影響測試結果。解決辦法其實很簡單,再把異常拋出來就行了。
3.參考代碼:
# coding:utf-8 from selenium import webdriver import time,unittest from selenium.webdriver.support import expected_conditions as EC
class Login(unittest.TestCase): def setUp(self): url_login = "https://passport.cnblogs.com/user/signin" self.driver = webdriver.Firefox() self.driver.get(url_login) def test_01(self): '''前面輸入賬號密碼,讓正確運行到assert這一步,斷言故意設置為Fals e不成功''' try: self.driver.find_element_by_id("input1").send_keys(u"上海-悠悠") self.driver.find_element_by_id("input2").send_keys("xxx") # 登錄id是錯的,定位會拋異常 self.driver.find_element_by_id("signin").click() # 判斷登錄成功頁面是否有賬號:"上海-悠悠" time.sleep(3) locator = ("id", "lnk_current_user") result = EC.text_to_be_present_in_element(locator,u"上海-悠悠")(self.driver) self.assertFalse(result) except Exception as msg: print(u"異常原因%s"%msg) # 圖片名稱可以加個時間戳 nowTime = time.strftime("%Y%m%d.%H.%M.%S") self.driver.get_screenshot_as_file('%s.jpg' % nowTime) raise def tearDown(self): self.driver.quit()
if __name__ == "__main__": unittest.main()
4.運行結果:
異常原因True is not false
Failure
Traceback (most recent call last):
File "D:\test\yoyot\ketang\test01.py", line 22, in test_01
self.assertFalse(result)
AssertionError: True is not false
1.13 練習題4:郵件發送(smtp)
前言
本篇總結了QQ郵箱和163郵箱發送郵件,郵件包含html中文和附件,可以發給多個收件人,專治各種不行,總之看完這篇麻麻再也不用擔心我的郵件收不到了。
以下代碼兼容python2和python3,運行無異常,放心大膽食用。
一、163郵箱
1.先導入smtplib庫用來發送郵件,導入MIMEText庫用來做純文本的郵件模板
2.先准備幾個跟發郵件相關的參數,每個郵箱的發件服務器都不一樣,以163為例,百度搜到發件服務器為:smtp.163.com
3.接下來就是寫郵件的主題和正文內容,正文這里用html格式的
4.最后調用SMTP發件服務
5.參考代碼:
# coding:utf-8 import smtplib from email.mime.text import MIMEText
# ----------1.跟發件相關的參數------ smtpserver = "smtp.163.com" # 發件服務器 port = 0 # 端口 sender = "yoyo@163.com" # 賬號 psw = "**************" # 密碼 receiver = "283340479@qq.com" # 接收人
# ----------2.編輯郵件的內容------ subject = "這個是主題163" body = '<p>這個是發送的163郵件</p>' # 定義郵件正文為html格式 msg = MIMEText(body, "html", "utf-8") msg['from'] = sender msg['to'] = "283340479@qq.com" msg['subject'] = subject
# ----------3.發送郵件------ smtp = smtplib.SMTP() smtp.connect(smtpserver) # 連服務器 smtp.login(sender, psw) # 登錄 smtp.sendmail(sender, receiver, msg.as_string()) # 發送 smtp.quit() # 關閉
二、QQ郵件
1.QQ郵箱是需要SSL認證的,這種郵箱跟上面的就有點不一樣了。
2.找到QQ郵箱授權碼,打開QQ郵箱-設置-賬號-POP3開啟服務-開啟
(如果已經開啟了,不知道授權碼,就點溫馨提示里面的‘生成授權碼’)
3.發驗證短信獲取授權碼,照着提示發個短信,如何點我已發送,就會收到授權碼了。
4.收到授權碼后復制,保存下來,這個就可以當QQ郵箱的密碼了。
5.QQ郵箱發送郵件代碼,跟163有點不一樣,如下圖紅色框框:
6.參考代碼:
# coding:utf-8 import smtplib from email.mime.text import MIMEText
# ----------1.跟發件相關的參數------ # smtpserver = "smtp.163.com" # 發件服務器 smtpserver = "smtp.qq.com" port = 465 # 端口 sender = "283340479@qq.com" # 賬號 psw = "**************" # 密碼 receiver = "283340479@qq.com" # 接收人
# ----------2.編輯郵件的內容------ subject = "這個是主題QQ" body = '<p>這個是發送的QQ郵件</p>' # 定義郵件正文為html格式 msg = MIMEText(body, "html", "utf-8") msg['from'] = sender msg['to'] = "283340479@qq.com" msg['subject'] = subject
# ----------3.發送郵件------ # smtp = smtplib.SMTP() # smtp.connect(smtpserver) # 連服務器 smtp = smtplib.SMTP_SSL(smtpserver, port) smtp.login(sender, psw) # 登錄 smtp.sendmail(sender, receiver, msg.as_string()) # 發送 smtp.quit() # 關閉
三、兼容163和QQ郵箱
1.如果想兼容上面兩種方式發送郵件,只需把第三塊內容稍微改下,如下所示
四、發送帶附件
1.上面的MIMEText只能發送正文,無法帶附件,發送帶附件的需要導入另外一個模塊MIMEMultipart
2.先讀取要發送文件的內容,file_path是路徑的參數名
3.下圖紅色框框file_name參數是發送的附件重新命名
4.參考代碼:
# coding:utf-8 import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart
# ----------1.跟發件相關的參數------ smtpserver = "smtp.163.com" # 發件服務器 port = 0 # 端口 sender = "yoyo@163.com" # 賬號 psw = "***********" # 密碼 receiver = "283340479@qq.com" # 接收人
# ----------2.編輯郵件的內容------ # 讀文件 file_path = "result.html" with open(file_path, "rb") as fp: mail_body = fp.read() msg = MIMEMultipart() msg["from"] = sender # 發件人 msg["to"] = receiver # 收件人 msg["subject"] = "這個我的主題" # 主題 # 正文 body = MIMEText(mail_body, "html", "utf-8") msg.attach(body) # 附件 att = MIMEText(mail_body, "base64", "utf-8") att["Content-Type"] = "application/octet-stream" att["Content-Disposition"] = 'attachment; filename="test_report.html"' msg.attach(att)
# ----------3.發送郵件------ try: smtp = smtplib.SMTP() smtp.connect(smtpserver) # 連服務器 smtp.login(sender, psw) except: smtp = smtplib.SMTP_SSL(smtpserver, port) smtp.login(sender, psw) # 登錄 smtp.sendmail(sender, receiver, msg.as_string()) # 發送 smtp.quit()
5.最后結果,有圖有真相
五、發給多個收件人
1.上面都是發給一個收件人,那么如何一次發給多個收件人呢?只需改兩個小地方
2.把receiver參數改成list對象,單個多個都是可以收到的
3.msg["to"]這個參數不能用list了,得先把receiver參數轉化成字符串,如下圖所示
4.參考代碼:
# coding:utf-8 import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart
# ----------1.跟發件相關的參數------ smtpserver = "smtp.163.com" # 發件服務器 port = 0 # 端口 sender = "yoyo@163.com" # 賬號 psw = "*********" # 密碼 # receiver = ["xxxx@qq.com"] # 單個接收人也可以是list receiver = ["xxxx@qq.com", "yoyo@qq.com"] # 多個收件人list對象
# ----------2.編輯郵件的內容------ # 讀文件 file_path = "result.html" with open(file_path, "rb") as fp: mail_body = fp.read() msg = MIMEMultipart() msg["from"] = sender # 發件人 msg["to"] = ";".join(receiver) # 多個收件人list轉str msg["subject"] = "這個我的主題999" # 主題 # 正文 body = MIMEText(mail_body, "html", "utf-8") msg.attach(body) # 附件 att = MIMEText(mail_body, "base64", "utf-8") att["Content-Type"] = "application/octet-stream" att["Content-Disposition"] = 'attachment; filename="test_report.html"' msg.attach(att)
# ----------3.發送郵件------ try: smtp = smtplib.SMTP() smtp.connect(smtpserver) # 連服務器 smtp.login(sender, psw) except: smtp = smtplib.SMTP_SSL(smtpserver, port) smtp.login(sender, psw) # 登錄 smtp.sendmail(sender, receiver, msg.as_string()) # 發送 smtp.quit() # 關閉
六:郵件收不到的幾種原因:
1.Subject和正文內容不要用hello、hehe、test等單詞
2.from(發件人)和to(收件人)不要為空,
(要不然會被認為是垃圾郵件)
3.找不到的話,先看下垃圾信箱,是不是跑到垃圾箱了
4.如果前幾次可以收到,后來收不到了,需改下subject內容
(因為每次都是一個subject,系統也會拒收的,把subject內容設置為動態的是最好的)
5.部分郵箱是ssl加密了的,所以無法發送,如:qq郵箱
(用授權碼去登錄)
6.要是按照上面的步驟來報錯了,說明代碼抄錯了,多檢查幾次。
(以上代碼均在python2和python3上都測試通過了)
1.14 unittest之skip
前言
當測試用例寫完后,有些模塊有改動時候,會影響到部分用例的執行,這個時候我們希望暫時跳過這些用例。
或者前面某個功能運行失敗了,后面的幾個用例是依賴於這個功能的用例,如果第一步就失敗了,后面的用例也就沒必要去執行了,直接跳過就行,節省用例執行時間。
一、skip裝飾器
skip裝飾器一共有四個:
@unittest.skip(reason)
Unconditionally skip the decorated test. reason should describe why the test is being skipped.
翻譯:無條件跳過用例,reason是說明原因。
@unittest.skipIf(condition, reason)
Skip the decorated test if condition is true.
翻譯:condition為true的時候跳過。
@unittest.skipUnless(condition, reason)
Skip the decorated test unless condition is true.
翻譯:condition為False的時候跳過。
@unittest.expectedFailure
Mark the test as an expected failure. If the test fails when run, the test is not counted as a failure.
翻譯:斷言的時候跳過。
二、skip案例
運行結果:
測試1
測試4
.ssx
----------------------------------------------------------------------
Ran 4 tests in 0.003s
OK (skipped=2, expected failures=1)
三、跳過整個測試類
四、參考代碼:
# coding:utf-8 import unittest class Test(unittest.TestCase): @unittest.skip(u"無條件跳過此用例") def test_1(self): print "測試1" @unittest.skipIf(True, u"為True的時候跳過") def test_2(self): print "測試2" @unittest.skipUnless(False, u"為False的時候跳過") def test_3(self): print "測試3" @unittest.expectedFailure def test_4(self): print "測試4" self.assertEqual(2, 4, msg=u"判斷相等") if __name__ == "__main__": unittest.main()