Python之学会测试,让开发更加高效(一)

简介:

Python之学会测试,让开发更加高效(一)

前几天,听了公司某位大佬关于编程心得的体会,其中讲到了“测试驱动开发”,感觉自己的测试技能薄弱,因此,写下这篇文章,希望对测试能有个入门。这段时间,笔者也体会到了测试的价值,一句话,学会测试,能够让你的开发更加高效。
本文将介绍以下两个方面的内容:

Test with Coverage
Mock
Test with Coverage
测试覆盖率通常被用来衡量测试的充分性和完整性。从广义的角度讲,主要分为两大类:面向项目的需求覆盖率和更偏向技术的代码覆盖率。对于开发人员来说,我们更注重代码覆盖率。
代码覆盖率指的是至少执行了一次的条目数占整个条目数的百分比。如果条目数是语句,对应的就是代码行覆盖率;如果条目数是函数,对应的就是函数覆盖率;如果条目数是路径,对应的就是路径覆盖率,等等。统计代码覆盖率的根本目的是找出潜在的遗漏测试用例,并有针对性的进行补充,同时还可以识别出代码中那些由于需求变更等原因造成的废弃代码。通常我们希望代码覆盖率越高越好,代码覆盖率越高越能说明你的测试用例设计是充分且完备的,但测试的成本会随着代码覆盖率的提高而增加。
在Python中,coverage模块帮助我们实现了代码行覆盖率,我们可以方便地使用它来完整测试的代码行覆盖率。
我们通过一个例子来介绍coverage模块的使用。
首先,我们有脚本func_add.py,实现了add函数,代码如下:

-- 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

在add函数中,分四种情况实现了加法,分别是字符串,列表,属性值,以及其它情况。
接着,我们用unittest模块来进行单元测试,代码脚本(test_func_add.py)如下:

import unittest
from func_add import add

class Test_Add(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(Test_Add('test_add_case1'))
suite.addTest(Test_Add('test_add_case2'))
# suite.addTest(Test_Add('test_add_case3'))
# suite.addTest(Test_Add('test_add_case4'))
run = unittest.TextTestRunner()
run.run(suite)

在这个测试中,我们只测试了前两个用例,也就是对字符串和数值型的加法进行测试。
在命令行中输入coverage run test_func_add.py命令运行该测试脚本,输出结果如下:

Hello+World
.3

.

Ran 2 tests in 0.000s

OK
再输入命令coverage html就能生成代码行覆盖率的报告,会生成htmlcov文件夹,打开其中的index.html文件,就能看到本次执行的覆盖率情况,如下图:

我们点击func_add.py查看add函数测试的情况,如下图:

可以看到,单元测试脚本test_func_add.py的前两个测试用例只覆盖到了add函数中左边绿色的部分,而没有测试到红色的部分,代码行覆盖率为75%。
因此,还有两种情况没有覆盖到,说明我们的单元测试中的测试用例还不够充分。
在test_func_add.py中,我们把main函数中的注释去掉,把后两个测试用例也添加进来,这时候我们再运行上面的coverage模块的命令,重新生成htmlcov后,func_add.py的代码行覆盖率如下图:

可以看到,增加测试用例后,我们调用的add函数代码行覆盖率为100%,所有的代码都覆盖到了。

Mock
Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西。准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代掉指定的Python对象,以达到模拟对象的行为。在Python3中,mock是辅助单元测试的一个模块。它允许您用模拟对象替换您的系统的部分,并对它们已使用的方式进行断言。
在实际生产中的项目是非常复杂的,对其进行单元测试的时候,会遇到以下问题:

接口的依赖
外部接口调用
测试环境非常复杂
单元测试应该只针对当前单元进行测试, 所有的内部或外部的依赖应该是稳定的, 已经在别处进行测试过的。使用mock 就可以对外部依赖组件实现进行模拟并且替换掉, 从而使得单元测试将焦点只放在当前的单元功能。
我们通过一个简单的例子来说明mock模块的使用。
首先,我们有脚本mock_multipy.py,主要实现的功能是Operator类中的multipy函数,在这里我们可以假设该函数并没有实现好,只是存在这样一个函数,代码如下:

-- coding: utf-8 --

mock_multipy.py

class Operator():

def multipy(self, a, b):
    pass

尽管我们没有实现multipy函数,但是我们还是想对这个函数的功能进行测试,这时候我们可以借助mock模块中的Mock类来实现。测试的脚本(mock_example.py)代码如下:

-- coding: utf-8 --

from unittest import mock
import unittest

from mock_multipy import Operator

test Operator class

class TestCount(unittest.TestCase):

def test_add(self):
    op = Operator()
    # 利用Mock类,我们假设返回的结果为15
    op.multipy = mock.Mock(return_value=15)
    # 调用multipy函数,输入参数为4,5,实际并未调用
    result = op.multipy(4, 5)
    # 声明返回结果是否为15
    self.assertEqual(result, 15)

if name == '__main__':

unittest.main()

让我们对上述的代码做一些说明。

op.multipy = mock.Mock(return_value=15)
通过Mock类来模拟调用Operator类中的multipy()函数,return_value 定义了multipy()方法的返回值。

result = op.multipy(4, 5)
result值调用multipy()函数,输入参数为4,5,但实际并未调用,最后通过assertEqual()方法断言,返回的结果是否是预期的结果为15。输出的结果如下:

Ran 1 test in 0.002s

OK
通过Mock类,我们即使在multipy函数并未实现的情况下,仍然能够通过想象函数执行的结果来进行测试,这样如果有后续的函数依赖multipy函数,也并不影响后续代码的测试。
利用Mock模块中的patch函数,我们可以将上述测试的脚本代码简化如下:

-- coding: utf-8 --

import unittest

from unittest.mock import patch
from mock_multipy import Operator

test Operator class

class TestCount(unittest.TestCase):

@patch("mock_multipy.Operator.multipy")
def test_case1(self, tmp):
    tmp.return_value = 15
    result = Operator().multipy(4, 5)
    self.assertEqual(15, result)

if name == '__main__':

unittest.main()

patch()装饰器可以很容易地模拟类或对象在模块测试。在测试过程中,您指定的对象将被替换为一个模拟(或其他对象),并在测试结束时还原。
那如果我们后面又实现了multipy函数,是否仍然能够测试呢?
修改mock_multipy.py脚本,代码如下:

-- coding: utf-8 --

mock_multipy.py

class Operator():

def multipy(self, a, b):
    return a * b

这时候,我们再运行mock_example.py脚本,测试仍然通过,这是因为multipy函数返回的结果仍然是我们mock后返回的值,而并未调用真正的Operator类中的multipy函数。
我们修改mock_example.py脚本如下:

-- coding: utf-8 --

from unittest import mock
import unittest

from mock_multipy import Operator

test Operator class

class TestCount(unittest.TestCase):

def test_add(self):
    op = Operator()
    # 利用Mock类,添加side_effect参数
    op.multipy = mock.Mock(return_value=15, side_effect=op.multipy)
    # 调用multipy函数,输入参数为4,5,实际已调用
    result = op.multipy(4, 5)
    # 声明返回结果是否为15
    self.assertEqual(result, 15)

if name == '__main__':

unittest.main()

side_effect参数和return_value参数是相反的。它给mock分配了可替换的结果,覆盖了return_value。简单的说,一个模拟工厂调用将返回side_effect值,而不是return_value。所以,设置side_effect参数为Operator类中的multipy函数,那么return_value的作用失效。
运行修改后的测试脚本,测试结果如下:

Ran 1 test in 0.004s

FAILED (failures=1)

15 != 20

Expected :20
Actual :15
可以发现,multipy函数返回的值为20,不等于我们期望的值15,这是side_effect函数的作用结果使然,返回的结果调用了Operator类中的multipy函数,所以返回值为20。
在self.assertEqual(result, 15)中将15改成20,运行测试结果如下:

Ran 1 test in 0.002s

OK
本次分享到此结束,感谢大家的阅读~

原文地址https://www.cnblogs.com/jclian91/p/12784994.html

相关文章
|
14天前
|
算法 测试技术 开发者
性能优化与代码审查:提升Python开发效率
【4月更文挑战第9天】本文强调了Python开发中性能优化和代码审查的重要性。性能优化包括选择合适数据结构、使用生成器和避免全局变量,而代码审查涉及遵循编码规范、使用静态代码分析工具和编写单元测试。这些实践能提升代码效率和可维护性,促进团队协作。
|
1天前
|
前端开发 测试技术 C++
Python自动化测试面试:unittest、pytest与Selenium详解
【4月更文挑战第19天】本文聚焦Python自动化测试面试,重点讨论unittest、pytest和Selenium三大框架。unittest涉及断言、TestSuite和覆盖率报告;易错点包括测试代码冗余和异常处理。pytest涵盖fixtures、参数化测试和插件系统,要注意避免过度依赖unittest特性。Selenium的核心是WebDriver操作、等待策略和测试报告生成,强调智能等待和元素定位策略。掌握这些关键点将有助于提升面试表现。
7 0
|
1天前
|
XML Web App开发 测试技术
python的Web自动化测试
【4月更文挑战第16天】Python在Web自动化测试中广泛应用,借助Selenium(支持多浏览器交互)、BeautifulSoup(解析HTML/XML)、Requests(发送HTTP请求)和Unittest(测试框架)等工具。测试步骤包括环境搭建、编写测试用例、初始化浏览器、访问页面、操作元素、验证结果、关闭浏览器及运行报告。注意浏览器兼容性、动态内容处理和错误处理。这些组合能提升测试效率和质量。
11 6
|
1天前
|
测试技术 持续交付 数据库
python集成测试
【4月更文挑战第16天】在Python集成测试中,确保模块间正确交互是关键。选择合适的测试框架如`unittest`或`pytest`,定义全面的测试用例,编写测试代码并设置类似生产环境的测试环境。执行测试后分析修复问题,将测试整合到持续集成流程,以尽早发现并解决问题。例如,使用`pytest`,我们可以创建测试用例验证不同模块间的功能是否按预期协同工作。
8 2
|
5天前
|
前端开发 Java Go
开发语言详解(python、java、Go(Golong)。。。。)
开发语言详解(python、java、Go(Golong)。。。。)
|
8天前
|
前端开发 数据挖掘 API
使用Python中的Flask框架进行Web应用开发
【4月更文挑战第15天】在Python的Web开发领域,Flask是一个备受欢迎的轻量级Web框架。它简洁、灵活且易于扩展,使得开发者能够快速地构建出高质量的Web应用。本文将深入探讨Flask框架的核心特性、使用方法以及在实际开发中的应用。
|
10天前
|
Web App开发 测试技术 网络安全
|
12天前
|
JavaScript 前端开发 关系型数据库
金融技术解决方案:用Python和Vue开发加密货币交易平台
【4月更文挑战第11天】本文介绍了如何使用Python和Vue.js构建加密货币交易平台。首先确保安装了Python、Node.js、数据库系统和Git。后端可选择Flask或Django框架,通过RESTful API处理交易。前端利用Vue.js、Vuex和Vue Router创建用户友好的界面,并用Axios与后端通信。这种架构促进团队协作,提升代码质量和平台功能。
|
13天前
|
JavaScript 前端开发 Docker
全栈开发实战:结合Python、Vue和Docker进行部署
【4月更文挑战第10天】本文介绍了如何使用Python、Vue.js和Docker进行全栈开发和部署。Python搭配Flask创建后端API,Vue.js构建前端界面,Docker负责应用的容器化部署。通过编写Dockerfile,将Python应用构建成Docker镜像并运行,前端部分使用Vue CLI创建项目并与后端交互。最后,通过Nginx和另一个Dockerfile部署前端应用。这种组合提升了开发效率,保证了应用的可维护性和扩展性,适合不同规模的企业使用。
|
14天前
|
JSON 测试技术 持续交付
自动化测试与脚本编写:Python实践指南
【4月更文挑战第9天】本文探讨了Python在自动化测试中的应用,强调其作为热门选择的原因。Python拥有丰富的测试框架(如unittest、pytest、nose)以支持自动化测试,简化测试用例的编写与维护。示例展示了使用unittest进行单元测试的基本步骤。此外,Python还适用于集成测试、系统测试等,提供模拟外部系统行为的工具。在脚本编写实践中,Python的灵活语法和强大库(如os、shutil、sqlite3、json)助力执行复杂测试任务。同时,Python支持并发、分布式执行及与Jenkins、Travis CI等持续集成工具的集成,提升测试效率和质量。