測試工具coverage的高階使用
??在文章Python之單元測試使用的一點心得中,筆者介紹了自己在使用Python測試工具coverge的一點心得,包括:
使用coverage模塊計算代碼測試覆蓋率
使用coverage api計算代碼測試覆蓋率
coverage配置文件的使用
coverage badge的生成
??本文在此基礎(chǔ)上,將會介紹coverage的高階使用,包括:
Flask API測試
coverage多文件測試
coverage的Gitlab CI/CD集成
coverage badge生成
??本文中使用coverage的版本均為7.3.0。
Flask API測試
??在unittest測試框架如果對Flask API進(jìn)行測試時使用HTTP請求,那么將無法得到代碼覆蓋率。
??我們有如下的示例Flask服務(wù):
# -*- coding: utf-8 -*-
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello index"
@app.route('/test')
def test():
return "Hello test"
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000, debug=True)
??正確的測試代碼如下:
# -*- coding: utf-8 -*-
import unittest
from flask_app import app
class AppTestCase(unittest.TestCase):
def setUp(self):
self.ctx = app.app_context()
self.ctx.push()
self.client = app.test_client()
def tearDown(self):
self.ctx.pop()
def test_case1(self):
response = self.client.get("/")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.text, "Hello index")
def test_case2(self):
response = self.client.get("/test")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.text, "Hello test")
if __name__ == "__main__":
suite = unittest.TestSuite()
suite.addTest(AppTestCase('test_case1'))
suite.addTest(AppTestCase('test_case2'))
run = unittest.TextTestRunner()
run.run(suite)
coverage多文件測試
??我們有如下的實現(xiàn)兩個變量相加的代碼(func_add.py):
# -*- coding: utf-8 -*-
def add(a, b):
if isinstance(a, str) and isinstance(b, str):
return a + '+' + b
elif isinstance(a, list) and isinstance(b, list):
return a + b
elif isinstance(a, (int, float)) and isinstance(b, (int, float)):
return a + b
else:
return None
??兩個測試文件test_func_add1.py和test_func_add2.py,內(nèi)容如下:
# -*- coding: utf-8 -*-
import unittest
from func_add import add
class TestAdd(unittest.TestCase):
def setUp(self):
pass
def test_add_case1(self):
a = "Hello"
b = "World"
res = add(a, b)
print(res)
self.assertEqual(res, "Hello+World")
def test_add_case2(self):
a = 1
b = 2
res = add(a, b)
print(res)
self.assertEqual(res, 3)
if __name__ == '__main__':
# 部分用例測試
# 構(gòu)造一個容器用來存放我們的測試用例
suite = unittest.TestSuite()
# 添加類中的測試用例
suite.addTest(TestAdd('test_add_case1'))
suite.addTest(TestAdd('test_add_case2'))
run = unittest.TextTestRunner()
run.run(suite)
# -*- coding: utf-8 -*-
import unittest
from func_add import add
class TestAdd(unittest.TestCase):
def setUp(self):
pass
def test_add_case3(self):
a = [1, 2]
b = [3]
res = add(a, b)
print(res)
self.assertEqual(res, [1, 2, 3])
def test_add_case4(self):
a = 2
b = "3"
res = add(a, b)
print(None)
self.assertEqual(res, None)
if __name__ == '__main__':
# 部分用例測試
# 構(gòu)造一個容器用來存放我們的測試用例
suite = unittest.TestSuite()
# 添加類中的測試用例
suite.addTest(TestAdd('test_add_case3'))
suite.addTest(TestAdd('test_add_case4'))
run = unittest.TextTestRunner()
run.run(suite)
使用命令進(jìn)行測試:
coverage run test_func_add1.py
coverage run test_func_add2.py
coverage report
生成的代碼測試覆蓋率如下:
Name Stmts Miss Cover
---------------------------------
func_add.py 8 2 75%
---------------------------------
TOTAL 8 2 75%
這是不符合我們預(yù)期的,因為在這兩個測試文件中我們對所有的代碼都進(jìn)行了測試,理論上測試覆蓋率應(yīng)該為100%,之所以這樣,是因為coverage run命令運行時每一次都會覆蓋掉之前的測試。正確的測試命令(以文件追加的形式)如下:
coverage run test_func_add1.py
coverage run --append test_func_add2.py
coverage report
此時代碼覆蓋率如下:
Name Stmts Miss Cover
---------------------------------
func_add.py 8 0 100%
---------------------------------
TOTAL 8 0 100%
coverage的Gitlab CI/CD集成
??在文章Gitlab CI/CD入門(一)Python項目的CI演示中,筆者介紹了Gitlab CI/CD的入門。在此基礎(chǔ)上,我們將集成coverage。
??首先我們的test目錄如下:
.
├── __init__.py
├── func_add.py
└── test_func_add.py
func_add.py為實現(xiàn)兩個變量相加的代碼,如前述。test_func_add.py為測試代碼,如下:
# -*- coding: utf-8 -*-
import unittest
from func_add import add
class TestAdd(unittest.TestCase):
def setUp(self):
pass
def test_add_case1(self):
a = "Hello"
b = "World"
res = add(a, b)
print(res)
self.assertEqual(res, "Hello+World")
def test_add_case2(self):
a = 1
b = 2
res = add(a, b)
print(res)
self.assertEqual(res, 3)
def test_add_case3(self):
a = [1, 2]
b = [3]
res = add(a, b)
print(res)
self.assertEqual(res, [1, 2, 3])
def test_add_case4(self):
a = 2
b = "3"
res = add(a, b)
print(None)
self.assertEqual(res, None)
if __name__ == '__main__':
# 部分用例測試
# 構(gòu)造一個容器用來存放我們的測試用例
suite = unittest.TestSuite()
# 添加類中的測試用例
suite.addTest(TestAdd('test_add_case1'))
suite.addTest(TestAdd('test_add_case2'))
suite.addTest(TestAdd('test_add_case3'))
suite.addTest(TestAdd('test_add_case4'))
run = unittest.TextTestRunner()
run.run(suite)
CI/CD依賴.gitlab-ci.yml,配置如下:
stages:
- build
- unittest
build-job:
stage: build
script:
- echo `date`
- echo "Hello, $GITLAB_USER_LOGIN!"
- echo "This job deploys something from the $CI_COMMIT_BRANCH branch."
unit_test_job:
stage: unittest
image: python:3.9-alpine3.17
script:
- pip3 install coverage==7.3.0
- coverage run test/test_func_add.py
- coverage report
coverage: '/TOTAL.*\s+(\d+%)$/'
??運行CI/CD,結(jié)果如下圖:
unittest_job運行結(jié)果
??在Gitlab項目中的
Settings -> CI/CD -> General pipelines中點擊Expand,會顯示CI/CD已內(nèi)置Pipeline status, Coverage report, Latest release,其中Coverage repor如下圖:
Coverage report
??最后我們要在項目中加入coverage badge(徽章),在Gitlab項目中的
Settings -> General -> Badge中點擊Expand,再點擊Add badge,coverage徽章的配置如下:
Add badge
本項目中只有main分支,因此不需要設(shè)置變量,實際在使用過程中,需要配置變量如default_branch等。
??以上配置完畢后,項目徽章顯示如下:
成功加入徽章!
??以上配置過程已開源,項目網(wǎng)址為:https://gitlab.com/jclian91/gitlab_ci_test 。
coverage badge生成
??coverage badge生成方式分為靜態(tài)和動態(tài)。
??動態(tài)的話,可使用coverage-badge或者genbadge模塊。
??靜態(tài)的話,可使用網(wǎng)站:https://shields.io/badges/static-badge .
??比如我們生成編程語言的徽章,如下圖:
示例徽章生成
之后我們就可以用該網(wǎng)址訪問徽章了。
總結(jié)
??本文介紹了測試工具coverage的高階使用,希望能對讀者有所啟發(fā)~
