新聞中心
前兩天在 GitHub 瀏覽 Python 的三方庫(kù)時(shí),看到了以下

創(chuàng)新互聯(lián)公司是一家從事企業(yè)網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、行業(yè)門(mén)戶(hù)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)制作的專(zhuān)業(yè)網(wǎng)站制作公司,擁有經(jīng)驗(yàn)豐富的網(wǎng)站建設(shè)工程師和網(wǎng)頁(yè)設(shè)計(jì)人員,具備各種規(guī)模與類(lèi)型網(wǎng)站建設(shè)的實(shí)力,在網(wǎng)站建設(shè)領(lǐng)域樹(shù)立了自己獨(dú)特的設(shè)計(jì)風(fēng)格。自公司成立以來(lái)曾獨(dú)立設(shè)計(jì)制作的站點(diǎn)上1000+。
就像 https 那個(gè)綠色鎖的標(biāo)志一樣,看著很可信,讓人用著放心,很多開(kāi)源項(xiàng)目都有這些圖標(biāo)。
看到 coverage 是 98%,我產(chǎn)生了疑問(wèn),這是手工統(tǒng)計(jì)的,還是程序自動(dòng)測(cè)試出來(lái)的呢?
如果是手工統(tǒng)計(jì)的,肯定都往高了寫(xiě),這樣的數(shù)據(jù)也就沒(méi)有價(jià)值,如果是程序自動(dòng)測(cè)試出來(lái)的,想著都覺(jué)得復(fù)雜,是怎么實(shí)現(xiàn)的呢?帶著這些疑問(wèn),我點(diǎn)擊了那個(gè) coverage 98%,跳轉(zhuǎn)到了 https://coveralls.io/ 的頁(yè)面。
探索了一番,發(fā)現(xiàn)原來(lái)這是叫 coveralls 的三方庫(kù)實(shí)現(xiàn)的,用于在線(xiàn)實(shí)時(shí)顯示單元測(cè)試的覆蓋率,測(cè)試數(shù)據(jù)是通過(guò) coverage 來(lái)跑出來(lái)的。
好奇的我 pip install 安裝了下,拿自己之前的程序,寫(xiě)了幾個(gè)單元測(cè)試,用了下這兩條命令:
- coverage run --source=dbinterface -m pytest tests/
- coverage report -m
發(fā)現(xiàn),這個(gè)單元測(cè)試的覆蓋率果然是程序自動(dòng)統(tǒng)計(jì)出來(lái)的,coverage 真的太牛了,有了這個(gè),寫(xiě)單元測(cè)試就無(wú)法偷懶了,代碼質(zhì)量就有了量化標(biāo)準(zhǔn)。
從上面的圖中可以看到文件的哪些代碼行沒(méi)有測(cè)試到,然后針對(duì)性的編寫(xiě)單元測(cè)試。還可以生成 html 文件進(jìn)行查詢(xún),更為直觀(guān)。
猜測(cè) coverage 應(yīng)該是記錄了 pytest 調(diào)用的代碼行數(shù),然后從全部代碼行記錄中去除已經(jīng)測(cè)試過(guò)的行記錄,就是未測(cè)試的代碼行,從而統(tǒng)計(jì)覆蓋率。
當(dāng)時(shí),我不由自主發(fā)出了‘臥槽牛批’,不過(guò)仍然有疑問(wèn),程序是怎么檢測(cè)哪些代碼行被執(zhí)行了呢?雖然我知道 debug 時(shí)可以看到,但是如何寫(xiě)程序統(tǒng)計(jì),我還一無(wú)所知。
好奇心驅(qū)使著我去探索。
首先看下這個(gè) coverage 來(lái)自哪里,里面有什么內(nèi)容:
- (py38env) ? dbinterface git:(master) ? which coverage
- /Users/aaron/py38env/bin/coverage
可以看到 coverage 的內(nèi)容:
- (py38env) dbinterface git:(master) cat /Users/aaron/py38env/bin/coverage
- #!/Users/aaron/py38env/bin/python3
- # -*- coding: utf-8 -*-
- import re
- import sys
- from coverage.cmdline import main
- if __name__ == '__main__':
- sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
- sys.exit(main())
- (py38env) dbinterface git:(master)
其實(shí),在命令行執(zhí)行 coverage,就相當(dāng)于執(zhí)行:
- /Users/aaron/py38env/bin/python3 coverage
將該文件保存到一個(gè)目錄中,命名為 main.py,然后使用 PyCharm IDE 開(kāi)始調(diào)試,調(diào)試的過(guò)程中,發(fā)現(xiàn) coverage run --source=dbinterface -m pytest tests/ 命令會(huì)將測(cè)試的結(jié)果寫(xiě)入到文件 .coverage 中,再執(zhí)行 coverage report -m 時(shí)會(huì)從該文件統(tǒng)計(jì)出覆蓋率。
也就是說(shuō)關(guān)鍵是要弄清楚命令 coverage run --source=dbinterface -m pytest tests/ 的執(zhí)行過(guò)程。
繼續(xù) Debug,這里說(shuō)下,由于我們的命令是在路徑 /Users/aaron/github/somenzz/dbinterface 下執(zhí)行的,在 Debug 前,先使用 os.chdir 改變程序的工作目錄:
main.py
- #!/Users/aaron/py38env/bin/python3
- # -*- coding: utf-8 -*-
- import re
- import sys
- from coverage.cmdline import main
- import os
- if __name__ == '__main__':
- os.chdir('/Users/aaron/github/somenzz/dbinterface')
- sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
- print(sys.argv[0])
- sys.exit(main())
然后加入?yún)?shù),打好斷點(diǎn),追蹤。
最后追蹤到這里:
這里的 tracer 類(lèi)就是 CTracer,其源頭可以從 collector.py 文件的這段代碼看出來(lái):
可以看出 tracer 的原型要么是 CTracer, 要么是 PyTracer。
從作者的注釋中可以看到 CTracer 速度非???,而 PyTracer 相對(duì)較慢。
想看 CTracer 的源代碼,結(jié)果發(fā)現(xiàn)了這個(gè)文件
.so 文件相當(dāng)于 windows 的 dll 文件,是動(dòng)態(tài)鏈接庫(kù),需要反編譯成匯編語(yǔ)言,然后再分析執(zhí)行邏輯,這個(gè)對(duì)我來(lái)說(shuō)太難了,自己又不熟悉匯編,于是放棄。
那就只剩下 PyTracer 了,原理應(yīng)該是類(lèi)似的,PyTracer 的源代碼就是 pytracer.py 文件,可以直接打開(kāi)查看。
文件開(kāi)始的地方,導(dǎo)入了以下三個(gè)庫(kù):
- import atexit
- import dis
- import sys
- from coverage import env
其中前三個(gè)都是標(biāo)準(zhǔn)庫(kù),atexit 是退出處理器,可以注冊(cè)一個(gè)函數(shù),在解釋器終止時(shí)執(zhí)行。dis 是 Python 字節(jié)碼反匯編器,這兩個(gè)的使用只有一次,沒(méi)有派上大用處,可以忽略。
重點(diǎn)就是第三個(gè) sys 模塊,這個(gè)模塊和 os 模塊可以說(shuō)是博大精深,很多程序都會(huì)使用到,從包的名稱(chēng)也可以總結(jié)規(guī)律,名字越短,就越重要,其使用頻率就越高。
看 PyTracer 源代碼, sys.settrace 是起決定作用的,是 coverage 能夠統(tǒng)計(jì)單元測(cè)試覆蓋率的關(guān)鍵。
下面是對(duì) Python 官方文檔對(duì) sys.settrace 的介紹:
sys.settrace(tracefunc) 用來(lái)設(shè)置系統(tǒng)的跟蹤函數(shù),使得用戶(hù)在 Python 中就可以實(shí)現(xiàn) Python 源代碼調(diào)試器。該函數(shù)是特定于單個(gè)線(xiàn)程的,所以要讓調(diào)試器支持多線(xiàn)程,必須為正在調(diào)試的每個(gè)線(xiàn)程都用 settrace() 注冊(cè)一個(gè)跟蹤函數(shù),或使用 threading.settrace()。
跟蹤函數(shù)應(yīng)接收三個(gè)參數(shù):frame、event 和 arg。frame 是當(dāng)前的堆棧幀。event 是一個(gè)字符串:'call'、'line'、'return'、'exception' 或 'opcode'。arg 取決于事件類(lèi)型。
官方文檔 bb 這么多,說(shuō)實(shí)話(huà)我也沒(méi)太懂,到底咋用呢?我網(wǎng)上找了一個(gè)例子,比如說(shuō)文件 trace.py 內(nèi)容如下:
- import sys
- def stuff():
- print("calling stuff")
- def printer(frame, event, arg):
- print(frame, event, arg)
- return printer # return itself to keep tracing
- sys.settrace(printer)
- stuff()
也就是說(shuō)執(zhí)行函數(shù)之前,加上 sys.settrace。執(zhí)行該文件,可以得到以下結(jié)果:
- (py38env) tmp python trace.py
- call None
- line None
- calling stuff
- return None
- (py38env) tmp
程序執(zhí)行的行數(shù),執(zhí)行的操作都完整的顯示了出來(lái),將這些數(shù)據(jù)保存到文件中,就可以進(jìn)行單元測(cè)試覆蓋率的統(tǒng)計(jì)了。
雖然無(wú)法方便的查詢(xún) CTracer 源碼,但是從 PyTracer 這里還是學(xué)習(xí)到了 coverage 統(tǒng)計(jì)單元測(cè)試覆蓋率的統(tǒng)計(jì)方法。
一次偶遇 coveralls 讓我見(jiàn)識(shí)了 Python 原來(lái)還可以統(tǒng)計(jì)代碼的執(zhí)行情況,真的太秀了。
趁熱打鐵,我用 coveralls 的狀態(tài)圖標(biāo)也發(fā)布了一個(gè)工具庫(kù):dbinterface,單元測(cè)試覆蓋率 89%:
這個(gè)一個(gè)數(shù)據(jù)庫(kù)操作的通用接口,使用起來(lái)是相當(dāng)?shù)暮?jiǎn)單,從此讀寫(xiě)各種數(shù)據(jù)庫(kù)都不是事:
- from dbinterface.database_client import DataBaseClientFactory
- client1 = DataBaseClientFactory.create(
- dbtype="postgres",
- host="localhost",
- port=5432,
- user="postgres",
- pwd="121113",
- database="postgres",
- )
- client2 = DataBaseClientFactory.create(
- dbtype="mysql",
- host="localhost",
- port=3306,
- user="aaron",
- pwd="aaron",
- database="information_schema",
- )
- result1 = client1.read("select current_date")
- rows_affeted = client1.write(
- "insert into tmp_test_table values(%s, %s)", ("1", "aaron")
- )
- rows_export = client.export(
- "select * from information_schema.TABLES",
- params=(),
- file_path="/Users/aaron/tmp/mysql_tables.txt",
- delimeter="0x02",
- quote="0x03",
- all_col_as_str=False,
- )
- assert rows_export > 0
項(xiàng)目地址:https://github.com/somenzz/dbinterface
本文轉(zhuǎn)載自微信公眾號(hào)「Python七號(hào)」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Python七號(hào)公眾號(hào)。
文章題目:與Coveralls的不解之緣
轉(zhuǎn)載來(lái)于:http://m.5511xx.com/article/cdddgdd.html


咨詢(xún)
建站咨詢(xún)
