Test-driven development(TDD)開發模式在今天已經不是什么新奇的事了,它的開發思維是在開發一個產品功能的時候,先
編寫好該功能的測試代碼,在編寫開發比如,比如要寫二個數相除的函數,那么它的測試代碼應該為:
#!/usr/bin/env python #coding:utf-8 import unittest class TestDiv(unittest.TestCase): def setUp(self): pass def tearDown(self): pass def test_001(self): self.assertEqual(div(1,1),1) def test_002(self): self.assertRaises(ZeroDivisionError,div,1,0) if __name__=='__main__': unittest.main(verbosity=2)
執行如上的代碼,會提示如下NameError: global name 'div' is not defined的錯誤信息,事實上我們自己也是
非常明白,因為我們實際沒有實現這樣的一個函數,而是先寫了改函數功能的測試代碼,那么現在來寫函數部分,見
完善后的源碼:
#!/usr/bin/env python #coding:utf-8 def div(a,b): return a/b import unittest class TestDiv(unittest.TestCase): def setUp(self): pass def tearDown(self): pass def test_001(self): self.assertEqual(div(1,1),1) def test_002(self): self.assertRaises(ZeroDivisionError,div,1,0) if __name__=='__main__': unittest.main(verbosity=2)
再次執行我們的測試代碼,就會通過,見執行的結果結果:
這就是一個測試驅動的過程,關於測試驅動的開發模式以及實戰部分,建議看《Python Web開發測試驅動方法》這本書,在
里面作者圍繞Django的框架,有詳細的案例介紹和代碼論述。在這里我們只關注unittest框架,這也是本文章要總結的的部分。
不論對於開發還是測試,都離不開單元測試框架,對於開發而言使用單元測試框架,可以編寫測試代碼來驗證驗證自己編寫
的功能是否正確,對於測試而言,使用單元測試框架,可以編寫自動化的測試用例,在Python中單元測試框架是Pyunit,即
unittest,unittest我一直認為是一個很優秀的單元測試框架,至少我是這樣認為在,它是python的標准庫,官方詳細的地址
是:https://docs.python.org/2/library/unittest.html。單元測試支持測試自動化、 共享的安裝程序和關閉代碼測試、
聚合成集合,測試和報告框架從測試的獨立性。單元測試模塊提供可以很容易地支持這些素質的一組測試的類。關於unittest
測試框架建議可以到官方查看詳細的說明以及演示的實例。unittest各個模塊的關系為:
在一個完整的單元測試用例中,是包含了測試固件(setUp()和tearDown()),在測試執行的階段,我們更加願意使用測試
套件(TestSuite())來組織每個測試用例來執行(TestRunner)並得到測試結果(TestReport),什么 是測試固件,在
unittest中,setUp()與tearDown()被成為測試固件,某些人稱為鈎子(僅僅只一個稱呼而已),它的主要目標初始化
測試用例,執行測試用例后,對測試用例執行的結果做后期的處理,我們再看上面的測試用例,總共是二個測試用例,
不管執行那個測試用例,都會執行setUp()和tearDown(),也就是說,在一個測試類中,如果有N個測試用例,在執行
該測試類中的測試用例的時候,會執行N次setUp()和tearDown(),我們修改源碼並執行來看結果,見源碼:
#!/usr/bin/env python #coding:utf-8 def div(a,b): return a/b import unittest class TestDiv(unittest.TestCase): def setUp(self): print u'開始...' def tearDown(self): print u'結束...' def test_001(self): self.assertEqual(div(1,1),1) def test_002(self): self.assertRaises(ZeroDivisionError,div,1,0) if __name__=='__main__': unittest.main(verbosity=2)
見執行的結果截圖:
依據結果可以看到,執行了二個測試用例,也執行了2次setUp()和tearDown()方法,如果這樣你感覺不明顯,可以結合
selenium的測試框架來看更加直觀,見源碼:
#!/usr/bin/env python #coding:utf-8 def div(a,b): return a/b import unittest from selenium import webdriver class TestDiv(unittest.TestCase): def setUp(self): self.driver=webdriver.Firefox() self.driver.get('http://www.baidu.com') def tearDown(self): self.driver.quit() def test_001(self): self.assertEqual(self.driver.title,u'百度一下,你就知道') def test_002(self): self.assertEqual(self.driver.current_url,'https://www.baidu.com/') if __name__=='__main__': unittest.main(verbosity=2)
執二后,會看到打開瀏覽器二次,當然關閉瀏覽器也是二次,這里不在進行截圖了。那么可不可以讓測試固件只執行
一次了,也就是說在一個測試類中,有N個測試用例,執行這個測試類中的測試用例后,測試固件只執行一次。當然是
可以的,unittest提供了這樣的解決方案,在這里鈎子方法使用的是類方法(關於實例方法,類方法,靜態方法不熟悉
的建議看下python的OOP部分),我們重構下代碼來實現這樣的一個過程,見源碼:
#!/usr/bin/env python #coding:utf-8 import unittest from selenium import webdriver class TestDiv(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver=webdriver.Firefox() cls.driver.get('http://www.baidu.com') @classmethod def tearDownClass(cls): cls.driver.quit() def test_001(self): self.assertEqual(self.driver.title,u'百度一下,你就知道') def test_002(self): self.assertEqual(self.driver.current_url,'https://www.baidu.com/') if __name__=='__main__': unittest.main(verbosity=2)
OK,帶着疑問繼續觸發,在一個測試用例中,測試用例想有順序的執行該如何實現了,可以使用addTest()方法來實現,
也就是說,把需要執行的或者按順序的添加到測試套件中,見修改后的源碼:
#!/usr/bin/env python #coding:utf-8 import unittest from selenium import webdriver class TestDiv(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver=webdriver.Firefox() cls.driver.get('http://www.baidu.com') @classmethod def tearDownClass(cls): cls.driver.quit() def test_001(self): self.assertEqual(self.driver.title,u'百度一下,你就知道') def test_002(self): self.assertEqual(self.driver.current_url,'https://www.baidu.com/') if __name__=='__main__': suite=unittest.TestSuite() suite.addTest(TestDiv('test_001')) unittest.TextTestRunner(verbosity=2).run(suite)
或者我們可以把代碼重構為:
#!/usr/bin/env python #coding:utf-8 import unittest from selenium import webdriver class TestDiv(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver=webdriver.Firefox() cls.driver.get('http://www.baidu.com') @classmethod def tearDownClass(cls): cls.driver.quit() def test_001(self): self.assertEqual(self.driver.title,u'百度一下,你就知道') def test_002(self): self.assertEqual(self.driver.current_url,'https://www.baidu.com/') @staticmethod def suites(): tests=['test_001','test_002'] return unittest.TestSuite(map(TestDiv,tests)) if __name__=='__main__': unittest.TextTestRunner(verbosity=2).run(TestDiv.suites())
事實上,我個人不贊成使用addTest()方法,來把測試用例依次添加到測試套件中,理由非常簡單,
因為在一個測試類中,測試用例是非常多的,這樣添加或者刪除實在是浪費時間,我們可以把源碼
修改下,來實現執行一個測試,某些用例不執行的可以忽略,使用的方法是makeSuite(),見修改后
的源碼:
#!/usr/bin/env python #coding:utf-8 import unittest from selenium import webdriver class TestDiv(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver=webdriver.Firefox() cls.driver.get('http://www.baidu.com') @classmethod def tearDownClass(cls): cls.driver.quit() def test_001(self): self.assertEqual(self.driver.title,u'百度一下,你就知道') @unittest.skip(u'忽略該測試用例,謝謝!') def test_002(self): self.assertEqual(self.driver.current_url,'https://www.baidu.com/') if __name__=='__main__': suite=unittest.TestSuite(unittest.makeSuite(TestDiv)) unittest.TextTestRunner(verbosity=2).run(suite)
見執行后的截圖:
在這里總結的,只是個人喜好,每個人可以根據自己的實際情況來進行,這只是提供了一種選擇而已。還有另外
一種方法是TestLoader()加載測試類來執行測試類中的所有測試用例,見源碼:
#!/usr/bin/env python #coding:utf-8 import unittest from selenium import webdriver class TestDiv(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver=webdriver.Firefox() cls.driver.get('http://www.baidu.com') @classmethod def tearDownClass(cls): cls.driver.quit() def test_001(self): self.assertEqual(self.driver.title,u'百度一下,你就知道') def test_002(self): self.assertEqual(self.driver.current_url,'https://www.baidu.com/') if __name__=='__main__': suite=unittest.TestLoader().loadTestsFromTestCase(TestDiv) unittest.TextTestRunner(verbosity=2).run(suite)
在一個測試用例中,會有期望結果這個說法,來驗證這個測試用例是通過還是失敗,在unittest的測試框架
中,也提供了assert,我們先來看python中的斷言assert,來修改下源碼,看看python實際代碼的斷言,見
源碼:
#!/usr/bin/env python #coding:utf-8 import unittest from selenium import webdriver class TestDiv(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver=webdriver.Firefox() cls.driver.get('http://www.baidu.com') @classmethod def tearDownClass(cls): cls.driver.quit() def test_001(self): assert self.driver.title in u'百度一下,你就知道' def test_002(self): assert self.driver.current_url in 'https://www.baidu.com/' if __name__=='__main__': suite=unittest.TestLoader().loadTestsFromTestCase(TestDiv) unittest.TextTestRunner(verbosity=2).run(suite)
見執行的結果的截圖:
上面的僅僅是python語言自帶的assert,在unittest中提供了非常豐富的斷言,具體見如下的截圖:
下面就演示幾個斷言的使用方法,見案例的源碼:
#!/usr/bin/env python #coding:utf-8 import unittest from selenium import webdriver class TestDiv(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver=webdriver.Firefox() cls.driver.get('http://www.baidu.com') @classmethod def tearDownClass(cls): cls.driver.quit() def test_001(self): self.assertEqual(self.driver.title, u'百度一下,你就知道') def test_002(self): self.assertTrue(self.driver.find_element_by_id('kw').is_enabled()) def test_003(self): self.assertIsNot(self.driver.current_url,'www.baidu.com') if __name__=='__main__': suite=unittest.TestLoader().loadTestsFromTestCase(TestDiv) unittest.TextTestRunner(verbosity=2).run(suite)
見執行后的結果:
unittest的斷言是非常豐富的,這里就不在演示了,遇到了不知道,可以到官方查看。
在python中,提供了HTMLTestRunner.py來生成測試報告,把該文件下載后,直接放到C:\Python27\Lib的目錄下,
就可以導入該模塊使用了,見該實現的代碼:
#!/usr/bin/env python #coding:utf-8 import unittest from selenium import webdriver import HTMLTestRunner class TestDiv(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver=webdriver.Firefox() cls.driver.get('http://www.baidu.com') @classmethod def tearDownClass(cls): cls.driver.quit() def test_001(self): self.assertEqual(self.driver.title, u'百度一下,你就知道') def test_002(self): self.assertTrue(self.driver.find_element_by_id('kw').is_enabled()) def test_003(self): self.assertIsNot(self.driver.current_url,'www.baidu.com') if __name__=='__main__': suite=unittest.TestLoader().loadTestsFromTestCase(TestDiv) runner=HTMLTestRunner.HTMLTestRunner( stream=file('testReport.html','wb'), title=u'TestReport', description=u'測試報告詳細信息' ) runner.run(suite)
執行后,會在當前目錄下生成testReport.html的測試報告,見該報告的截圖:
python的unittest庫非常強大,這里只是介紹了一部分,詳細的可以到官方繼續查看或者關注本人的公眾號咨詢互相學習。如您對我寫的內容感興趣,可掃描關注本人的微信公眾,
祝安!