參考來源:Mushishi_xu博主和huilan_same同行的分享
前言
unittest是一個python版本的junit,junit是java中的單元測試框架,對java的單元測試,有一句話很貼切:Keep the bar green,相信使用eclipse寫過java單元測試的都心領神會。unittest實現了很多junit中的概念,作為標準python中的一個模塊,是其它框架和工具的基礎,參考資料是它的官方文檔:http://docs.python.org/2.7/library/unittest.html和源代碼,比如我們非常熟悉的test case, test suite等,總之,原理都是相通的,只是用不同的語言表達出來。
一、unittest工作原理
unittest中最核心的四個概念是:test case, test suite, test runner, test fixture。
一個TestCase的實例就是一個測試用例。什么是測試用例呢?就是一個完整的測試流程,包括測試前準備環境的搭建(setUp),執行測試代碼(run),以及測試后環境的還原(tearDown)。元測試(unit test)的本質也就在這里,一個測試用例是一個完整的測試單元,通過運行這個測試單元,可以對某一個問題進行驗證。
而多個測試用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite。
TestLoader是用來加載TestCase到TestSuite中的,其中有幾個loadTestsFrom__()方法,就是從各個地方尋找TestCase,創建它們的實例,然后add到TestSuite中,再返回一個TestSuite實例。
TextTestRunner是來執行測試用例的,其中的run(test)會執行TestSuite/TestCase中的run(result)方法。測試的結果會保存到TextTestResult實例中,包括運行了多少測試用例,成功了多少,失敗了多少等信息。
而對一個測試用例環境的搭建和銷毀,是一個fixture。
一個class繼承了unittest.TestCase,便是一個測試用例,但如果其中有多個以?test?開頭的方法,那么每有一個這樣的方法,在load的時候便會生成一個TestCase實例,如:一個class中有四個test_xxx方法,最后在load到suite中時也有四個測試用例。
到這里整個流程就清楚了:
寫好TestCase,然后由TestLoader加載TestCase到TestSuite,然后由TextTestRunner來運行TestSuite,運行的結果保存在TextTestResult中,我們通過命令行或者unittest.main()執行時,main會調用TextTestRunner中的run來執行,或者我們可以直接通過TextTestRunner來執行用例。這里加個說明,在Runner執行時,默認將執行結果輸出到控制臺,我們可以設置其輸出到文件,在文件中查看結果(通過HTMLTestRunner將結果輸出到HTML中,生成漂亮的報告,它跟TextTestRunner是一樣的)。
二、unittest實例-test case
1.準備測試方法
mathfunc.py
#coding:utf-8
import math
def add(a,b):
????return a + b
def minus(a,b):
????return a - b
def multi(a,b):
????return a * b
def divide(a,b):
????return a / b
2.為測試方法寫測試用例
run_mathfunc.py
#coding:utf-8
import unittest
from python_ceshikuangjia.mathfuncimport *
class TestMathFunc(unittest.TestCase):
????def test_add(self):
????????self.assertEqual(5,add(3.2))
????????self.assertNotEqual(3,add(2,2))
????def test_minus(self):
????????self.assertEqual(2,minus(4,2))
????def test_multi(self):
????????self.assertEqual(6,multi(2,3))
????def test_divide(self):
????????self.assertEqual(2,divide(6,3))
????????self.assertEqual(2.5,divide(5,2))
if __name__ =='__main__':
????unittest.main()
3.查看運行結果
這就是一個簡單的測試,有幾點需要說明的:
>在第一行給出了每一個用例執行的結果的標識,成功是?.,失敗是?F,出錯是?E,跳過是?S。從上面也可以看出,測試的執行跟方法的順序沒有關系,test_divide寫在了第4個,但是卻是第2個執行的。
>每個測試方法均以?test?開頭,否則是不被unittest識別的。
>在unittest.main()中加?verbosity?參數可以控制輸出的錯誤報告的詳細程度,默認是?1,如果設為?0,則不輸出每一用例的執行結果,即沒有上面的結果中的第1行;如果設為?2,則輸出詳細的執行結果,如下:
三、組織TestSuite
上面的代碼示例了如何編寫一個簡單的測試,但有兩個問題,我們怎么控制用例執行的順序呢?(這里的示例中的幾個測試方法并沒有一定關系,但之后你寫的用例可能會有先后關系,需要先執行方法A,再執行方法B),我們就要用到TestSuite了。我們添加到TestSuite中的case是會按照添加的順序執行的。
問題二是我們現在只有一個測試文件,我們直接執行該文件即可,但如果有多個測試文件,怎么進行組織,總不能一個個文件執行吧,答案也在TestSuite中。
請看run_suite.py
#coding:utf-8
import unittest
from python_ceshikuangjia.run_mathfuncimport TestMathFunc
if __name__ =='__main__':
????suite = unittest.TestSuite()
????tests = [TestMathFunc("test_add"),TestMathFunc("test_minus"),TestMathFunc("test_divide")]
????suite.addTests(tests)
????runner = unittest.TextTestRunner(verbosity=2)
????runner.run(suite)
運行結果:
可以看到,執行情況跟我們預料的一樣:執行了三個case,并且順序是按照我們添加進suite的順序執行的。那么,如何將結果輸出到文件呢,請看下面操作方法,修改run_suite.py代碼,如下:
#coding:utf-8
import unittest
from python_ceshikuangjia.run_mathfuncimport TestMathFunc
if __name__ =='__main__':
????suite = unittest.TestSuite()
????tests = [TestMathFunc("test_add"),TestMathFunc("test_minus"),TestMathFunc("test_divide")]
????suite.addTests(tests)
????with open('D:/work/python_ceshikuangjia/run_suite_log.txt','a')as f:
????????runner = unittest.TextTestRunner(stream=f,verbosity=2)
????????runner.run(suite)
四、test fixture之setUp() tearDown()
1.假如我的測試需要在每次執行之前準備環境,或者在每次執行完之后需要進行一些清理怎么辦?比如執行前需要連接數據庫,執行完成之后需要還原數據、斷開連接。總不能每個測試方法中都添加準備環境、清理環境的代碼吧,這時,就輪到test fixture之setUp() tearDown()大展身手的時候了,請看如下代碼:在run_mathfunc.py下的class TestMathFunc類中添加如下代碼
class TestMathFunc(unittest.TestCase):
????def setUp(self):
????????print("do something before test.Prepare environment.")
????def tearDown(self):
????????print("do something after test.Clean up.")
?setUp()?和?tearDown()?兩個方法(其實是重寫了TestCase的這兩個方法),這兩個方法在每個測試方法執行前以及執行后執行一次,setUp用來為測試準備環境,tearDown用來清理環境,已備之后的測試。
運行結果如下:
可以看到setUp和tearDown在每次執行case前后都執行了一次。
2.如果想要在所有case執行之前準備一次環境,并在所有case執行結束之后再清理環境,我們可以用?setUpClass()?與?tearDownClass():請看如下代碼:在run_mathfunc.py下的class?TestMathFunc類中添加如下代碼
class TestMathFunc(unittest.TestCase):
????@classmethod
? ? def setUpClass(cls):
????????print("This setUpClass() method only called once.")
????@classmethod
? ? def tearDownClass(cls):
????????print("This tearDownClass() method only called once too.")
運行結果:
可以看到setUpClass以及tearDownClass均只執行了一次。
3.運行測試用例時不想全部運行,或者說想跳過某一個用例,那么這時skip裝飾器就起作用了。
skip裝飾器一共有三個?unittest.skip(reason)、unittest.skipIf(condition, reason)、unittest.skipUnless(condition, reason),skip無條件跳過,skipIf當condition為True時跳過,skipUnless當condition為False時跳過。以下分兩種情況進行解析。
>skip裝飾器
class TestMathFunc(unittest.TestCase):
????@classmethod
? ? def setUpClass(cls):
????????print("This setUpClass() method only called once.")
????@classmethod
? ? def tearDownClass(cls):
????????print("This tearDownClass() method only called once too.")
? ? @unittest.skip(u"我不想運行此用例!!.")
????def test_add(self):
????????self.assertEqual(5,add(3,2))
????????self.assertNotEqual(3,add(2,2))
運行結果:
>TestCase.skipTest()方法
class TestMathFunc(unittest.TestCase):
????@classmethod
? ? def setUpClass(cls):
????????print("This setUpClass() method only called once.")
????@classmethod
? ? def tearDownClass(cls):
????????print("This tearDownClass() method only called once too.")
#? ? @unittest.skip(u"我不想運行此用例!!.")
? ? def test_add(self):
????????self.assertEqual(5,add(3,2))
????????self.assertNotEqual(3,add(2,2))
????def test_minus(self):
????????self.skipTest(u"我不想運行此用例!!")
????????self.assertEqual(2,minus(4,2))
運行結果:
通過以上兩種不同方式,可以看到總的test數量還是3個,但add()和minus()方法都被skip了。
五、用HTMLTestRunner輸出HTML報告
HTMLTestRunner是一個第三方的unittest HTML報告庫,首先我們下載HTMLTestRunner.py,并放到當前目錄下,或者你的’python’安裝目錄下,就可以導入運行了。
下載地址:HTMLTestRunner模板? (下載的模板只支持python2.x,要想在python3.x中使用可以看下這個:HTMLTestRunner修改成Python3版本)
修改我們的 run_suite.py:
#coding:utf-8
import unittest
import HTMLTestRunner
from python_ceshikuangjia.run_mathfuncimport TestMathFunc
if __name__ =='__main__':
????suite = unittest.TestSuite()
????tests = [TestMathFunc("test_add"),TestMathFunc("test_minus"),TestMathFunc("test_divide"),TestMathFunc('test_multi')]
????suite.addTests(tests)
# with open('D:/work/python_ceshikuangjia/run_suite_log.txt','a') as f:
#? ? runner = unittest.TextTestRunner(stream=f,verbosity=2)
#? ? runner.run(suite)
#輸出HTML格式報告
? ? with open('D:/work/python_ceshikuangjia/HTMLReport.html','wb')as f:
????????runner = HTMLTestRunner.HTMLTestRunner(stream=f,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? title=u'軟件測試報告 Test Report',
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? description=u'用例執行情況',
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? verbosity =2)
????????runner.run(suite)
運行結果:
這下漂亮的HTML報告也有了。其實你能發現,HTMLTestRunner的執行方法跟TextTestRunner很相似,你可以跟我上面的示例對比一下,就是把類圖中的runner換成了HTMLTestRunner,并將TestResult用HTML的形式展現出來,如果你研究夠深,可以寫自己的runner,生成更復雜更漂亮的報告。
單元測試小結:
1.unittest是Python自帶的單元測試框架,我們可以用其來作為我們自動化測試框架的用例組織執行框架。
2.unittest的流程:寫好TestCase,然后由TestLoader加載TestCase到TestSuite,然后由TextTestRunner來運行TestSuite,運行的結果保存在TextTestResult中,我們通過命令行或者unittest.main()執行時,main會調用TextTestRunner中的run來執行,或者我們可以直接通過TextTestRunner來執行用例。
3.項目命名不可用小寫‘test’開頭(大寫無影響),否則會出錯,一個class繼承unittest.TestCase即是一個TestCase,其中以?test?開頭的方法在load時被加載為一個真正的TestCase。
4.verbosity參數可以控制執行結果的輸出,0?是簡單報告、1?是一般報告、2?是詳細報告。
5.可以用?setUp()、tearDown()、setUpClass()以及?tearDownClass()可以在用例執行前布置環境,以及在用例執行后清理環境
6.我們可以通過skip,skipIf,skipUnless裝飾器跳過某個case,或者用TestCase.skipTest方法。
7.參數中加stream,可以將報告輸出到文件:可以用TextTestRunner輸出txt報告,以及可以用HTMLTestRunner輸出html報告,或者自己研究生成更復雜更漂亮的報告。