测试工具coverage的高阶使用

在文章Python之单元测试使用的一点心得中,笔者介绍了自己在使用Python测试工具coverge的一点心得,包括:

  1. 使用coverage模块计算代码测试覆盖率
  2. 使用coverage api计算代码测试覆盖率
  3. coverage配置文件的使用
  4. coverage badge的生成

本文在此基础上,将会介绍coverage的高阶使用,包括:

  • Flask API测试

  • coverage多文件测试

  • coverage的Gitlab CI/CD集成

  • coverage badge生成

    本文中使用coverage的版本均为7.3.0。

  • Flask API测试

    在unittest测试框架如果对Flask API进行测试时使用HTTP请求,那么将无法得到代码覆盖率。

    我们有如下的示例Flask服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -*- 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)

正确的测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# -*- 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多文件测试

我们有如下的实现两个变量相加的代码(func_add.py):

1
2
3
4
5
6
7
8
9
10
# -*- 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.pytest_func_add2.py,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# -*- 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__':

# 部分用例测试
# 构造一个容器用来存放我们的测试用例
suite = unittest.TestSuite()
# 添加类中的测试用例
suite.addTest(TestAdd('test_add_case1'))
suite.addTest(TestAdd('test_add_case2'))
run = unittest.TextTestRunner()
run.run(suite)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# -*- 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__':

# 部分用例测试
# 构造一个容器用来存放我们的测试用例
suite = unittest.TestSuite()
# 添加类中的测试用例
suite.addTest(TestAdd('test_add_case3'))
suite.addTest(TestAdd('test_add_case4'))
run = unittest.TextTestRunner()
run.run(suite)

使用命令进行测试:

1
2
3
coverage run test_func_add1.py
coverage run test_func_add2.py
coverage report

生成的代码测试覆盖率如下:

1
2
3
4
5
Name          Stmts   Miss  Cover
---------------------------------
func_add.py 8 2 75%
---------------------------------
TOTAL 8 2 75%

这是不符合我们预期的,因为在这两个测试文件中我们对所有的代码都进行了测试,理论上测试覆盖率应该为100%,之所以这样,是因为coverage run命令运行时每一次都会覆盖掉之前的测试。正确的测试命令(以文件追加的形式)如下:

1
2
3
coverage run test_func_add1.py
coverage run --append test_func_add2.py
coverage report

此时代码覆盖率如下:

1
2
3
4
5
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的入门。在此基础上,我们将集成coverage。

首先我们的test目录如下:

1
2
3
4
.
├── __init__.py
├── func_add.py
└── test_func_add.py

func_add.py为实现两个变量相加的代码,如前述。test_func_add.py为测试代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# -*- 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__':

# 部分用例测试
# 构造一个容器用来存放我们的测试用例
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,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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,结果如下图:

unittest_job运行结果

在Gitlab项目中的Settings -> CI/CD -> General pipelines中点击Expand,会显示CI/CD已内置Pipeline status, Coverage report, Latest release,其中Coverage repor如下图:

Coverage report

最后我们要在项目中加入coverage badge(徽章),在Gitlab项目中的Settings -> General -> Badge中点击Expand,再点击Add badge,coverage徽章的配置如下:

Add badge

本项目中只有main分支,因此不需要设置变量,实际在使用过程中,需要配置变量如default_branch等。

以上配置完毕后,项目徽章显示如下:

成功加入徽章!

以上配置过程已开源,项目网址为:https://gitlab.com/jclian91/gitlab_ci_test

coverage badge生成

coverage badge生成方式分为静态和动态。

动态的话,可使用coverage-badge或者genbadge模块。

静态的话,可使用网站:https://shields.io/badges/static-badge .

比如我们生成编程语言的徽章,如下图:

示例徽章生成

之后我们就可以用该网址访问徽章了。

总结

本文介绍了测试工具coverage的高阶使用,希望能对读者有所启发~

欢迎关注我的公众号NLP奇幻之旅,原创技术文章第一时间推送。

欢迎关注我的知识星球“自然语言处理奇幻之旅”,笔者正在努力构建自己的技术社区。


测试工具coverage的高阶使用
https://percent4.github.io/测试工具coverage的高阶使用/
作者
Jclian91
发布于
2023年8月19日
许可协议