文章目录
- 前言
- 一、使用pip安装pytest
- 1.1 更新pip
- 1.2 安装pytest
- 二、测试函数
- 2.1 编写测试文件
- 2.2 运行测试
- 2.3 测试不通过
- 2.4 测试不通过
- 2.4 增加新测试
- 三、测试类
- 3.1 断言
- 3.2 夹具
- 总结
前言
代码测试是程序开发中极其重要的一环,任何代码都应该经过测试才能上生产环境 。测试是为了检测代码是否能正确工作,产生和预期结果一致的输出,并且程序能否对异常或者不合理的输入给出反馈而不是直接崩溃。作为程序员,我们应该对自己的代码负责,经常测试自己的代码,在用户发现问题前找出问题,避免产生不必要的损失。
本文笔者将介绍Python中的一款测试工具 pytest
,通过编写测试用例来发现和解决代码中的潜在问题。pytest不是标准的Python库,是第三方库,我们需要手动安装它。
一、使用pip安装pytest
1.1 更新pip
pip是Python提供的一个用来安装第三方包的工具,以下是windows系统的执行更新pip命令,打开cmd命令行,最好是管理员权限
python -m pip install --upgrade pip
整个命令是先运行 pip 模块,再使用 install --upgrade 更新 pip包
注意:windows系统在我们安装python时就自动安装了pip,linux系统则不会自动安装pip
所以linux在安装python的同时安装pip的命令如下(安装了好几个包其实)
sudo apt install python3-dev python3-pip python3-venv
在windows系统下我们可以使用如下命令更新系统中任何已安装的包:
python -m pip install --upgrade package_name
1.2 安装pytest
pip升级到最新版候就可以安装pytest了
python -m pip install --user pytest
核心命令仍然时 pip install
,–user是只为当前用户安装
二、测试函数
先来段待测试的代码,创建一个模块name_function.py
其中的代码如下
def get_formatted_name(first, last):
"""Generate a neatly formatted full name."""
full_name = first + ' ' + last
return full_name.title()
这是一个简单的函数 get_formatted_name() 接受用户名和姓,返回一个完整的姓名
接着我们再建立一个模块name.py,导入并使用这个函数
from name_function import get_formatted_name
while True:
print("\nPlease tell me your name:")
print("(enter 'q' at any time to quit)")
f_name = input("First name: ")
if f_name == 'q':
break
l_name = input("Last name: ")
if l_name == 'q':
break
formatted_name = get_formatted_name(f_name, l_name)
print("\nHello, " + formatted_name + "!")
只要我们输入的姓和名都符合要求就能得到期望的结果
现在的问题是,当我们修改get_formatted_name()这个函数时,我们每次都要重新运行name.py这个测试模块代码,很烦锁。pytest就提供了一种自动测试函数输出的高效方式。
2.1 编写测试文件
针对name_function.py中的函数,我们编写一个测试的模块 test_name_function.py,代码如下
import name_function
def test_first_last_name():
"""Do names like 'Janis Joplin' work?"""
formatted_name = name_function.get_formatted_name('janis', 'joplin')
assert formatted_name == 'Janis Joplin'
注意测试文件的命名:以test_开头,这样pytest会查找所有test_开头的文件,运行其中的测试
测试文件中待测试的函数名称也必须以test_开头,这样pytest才会测试那些需要测试的函数,后面跟的一般是待测试函数的名称
2.2 运行测试
直接运行文件test_name_function.py不会有什么结果,因为没有调用test_first_last_name()这个函数
cmd命令进入test_name_function.py所在文件位置下直接运行
pytest
这个命令如果出现不是内部命令,说明pytest没有设置全局变量,或者安装错了位置。重新执行以下命令,观察安装位置
python -m pip install --user pytest
按照路径找到这个Scripts文件夹
点进去,发现两个两个可执行文件py.test.exe,pytest.exe
复制粘贴到你的python安装目录下的Scripts文件夹下
笔者使用的工具是Pycharm,使用这款编辑器内置的终端进入(读者可以使用cmd进入到文件所在目录),输入pytest
2.3 测试不通过
我们对name_funtion.py中的函数get_formatted_name()稍作修改,增加一个参数middle作为中间名,再使用pytest去测试
def get_formatted_name(first, last, middle=''):
"""Generate a neatly formatted full name."""
full_name = first + ' ' + middle + ' ' + last
return full_name.title()
根据测试不通过的错误信息反馈,缺少了一个last参数,因为测试代码中只传了两个参数,默认赋值给了get_formatted_name()函数中的前两个参数first和middle。
2.4 测试不通过
上述代码的测试不通过,我们可以通过增加测试时的传参来修复,但是测试代码我们一般是不做修改的。这意味着我们编写的新代码有错误不能兼容之前的逻辑,我们应该针对被测试的函数做修改。针对middle参数我们给出默认值,并分别判断其有值和无值时的逻辑走向
def get_formatted_name(first, last, middle=''):
"""Generate a neatly formatted full name."""
if middle:
full_name = first + ' ' + middle + ' ' + last
else:
full_name = first + ' ' + last
return full_name.title()
测试代码我们无须修改,继续使用pytest进行测试
2.4 增加新测试
被测试的函数get_formatted_name(),我们增加了一个可选参数middle,测试代码中仍然只有传递两个参数的测试函数test_first_last_name(),所以我们现在要在test_name_function.py中增加一个三参数的测试函数test_first_last_middle_name()
def test_first_last_middle_name():
"""Do names like 'Wolfgang Amadeus Mozart' work?"""
formatted_name = name_function.get_formatted_name(first='wolfgang', middle='amadeus', last='mozart')
assert formatted_name == 'Wolfgang Amadeus Mozart'
可以看到两个测试方法都成功运行通过了
三、测试类
前面展示的都是针对单个函数的测试,这一小节来看下编写针对类的测试。针对类的测试没问题了,说明其中的方法也都通过测试了。对类的修改没有破坏其原有的行为。
3.1 断言
断言就是程序中对某个变量进行条件判断是否成立的操作,条件成立则程序继续往下运行,否则就中断抛出异常。下面的一张表展示测试中常见的断言语句。
断言 | 用途 |
---|---|
assert a==b | 断言两个值相等 |
assert a!=b | 断言两个值不等 |
assert a | 断言a的布尔值为True |
assert not a | 断言a的布尔值为False |
assert element in list | 断言元素在列表中 |
assert element not in list | 断言元素不在列表中 |
这里列出的只是常见的断言,实际当中判断条件的代码都可以使用assert
创建模块calculator.py,代码如下
class Calculator:
def __init__(self):
# 初始化可以在这里添加必要的设置或变量
pass
def add(self, a, b):
"""返回两个数的和"""
return a + b
def subtract(self, a, b):
"""返回两数相减的结果"""
return a - b
def multiply(self, a, b):
"""返回两数相乘的结果"""
return a * b
def divide(self, a, b):
"""返回两数相除的结果,如果除数为0则返回None"""
if b == 0:
return None
return a / b
再创建test_calculator.py测试模块,分别测试上述计算类Calculator的四个方法
from calculator import Calculator
def test_add():
calc = Calculator()
assert calc.add(1, 2) == 3
def test_subtract():
calc = Calculator()
assert calc.subtract(1, 2) == -1
def test_multiply():
calc = Calculator()
assert calc.multiply(1, 2) == 2
def test_divide():
calc = Calculator()
assert calc.divide(1, 2) == 0.5
上述测试代码针对Calculator类中的四个函数都给出了对应的测试函数,使用pytest测试下
3.2 夹具
上面的测试类中,四个测试方法我们创建了四个被测试类Calculator的实例,简单示例这么写当然没问题,但是复杂项目中类有多个测试场景就变得棘手,难道每一个测试用例都要去创建一个类实例吗?夹具(fixture)就是解决该问题的,可以创建一个共享的资源供多个测试使用。pytest中使用@pytest.fixture来装饰函数,下面稍微修改下test_calculator.py模块
import pytest
from calculator import Calculator
@pytest.fixture(scope="module")
def get_calculator():
return Calculator()
def test_add(get_calculator):
assert get_calculator.add(1, 2) == 3
def test_subtract(get_calculator):
assert get_calculator.subtract(1, 2) == -1
def test_multiply(get_calculator):
assert get_calculator.multiply(1, 2) == 2
def test_divide(get_calculator):
assert get_calculator.divide(1, 2) == 0.5
提供一个函数获取Calculator的实例,方法上打上注解@pytest.fixture,需要导入pytest。接着每个测试方法都传入一个参数get_calculator(),即传入一个Calculator实例对象。
注意:@pytest.fixture(scope=“module”),我使用了作用域module,这样每个函数传入的get_calculator()获取的实例就是同一个。如果不适用scope,那么每个测试函数传入的都是一个新的Calculator实例。
总结
本章介绍的测试内容是软件开发中最重要的环节之一,然而大多数程序员却没有写测试代码的习惯,觉得没有必要或者只是简单的自测。这让我们写的代码就没有很可靠的保证,说来也惭愧,笔者开发年限也不短了,实际开发中也没有写测试用例的习惯。编写测试代码对于软件开发非常重要,主要原因包括以下几个方面:
- 保证代码质量:
- 发现错误:通过编写测试代码,可以在早期发现并修复潜在的错误和缺陷。
- 提高可靠性:确保代码按照预期工作,减少运行时错误的可能性。
- 文档作用:
- 清晰说明:测试代码可以作为文档的一部分,说明代码的功能和预期行为。
- 易于理解:新加入团队的成员可以通过测试代码快速了解系统的各个部分如何工作。
- 重构支持:
- 安全重构:在进行代码重构时,测试代码可以帮助验证改动后的代码仍然符合原有的功能要求。
- 减少风险:重构过程中如果有任何问题,测试代码可以迅速定位问题所在。
- 持续集成:
- 自动化测试:在持续集成环境中,自动化的测试代码可以确保每次提交代码后都能及时发现问题。
- 快速反馈:测试结果能够快速反馈给开发人员,帮助他们及时修正问题。
- 维护成本降低:
- 易于维护:良好的测试覆盖使得代码更容易维护,因为可以随时检查改动的影响。
- 减少回归错误:在添加新功能或修改现有功能时,测试代码可以防止引入新的错误。
- 团队协作:
- 共同理解:测试代码有助于团队成员之间达成共识,确保每个人都对系统的行为有相同的理解。
- 责任明确:测试代码可以明确指出哪些部分需要改进或修复,便于分配任务。
- 用户信心:
- 高质量产品:高质量的测试代码可以确保最终产品更加稳定可靠,增强用户的信任感。
- 减少用户反馈:减少因软件缺陷导致的用户反馈和支持请求。
- 设计驱动开发:
- 测试驱动开发 (TDD):通过先编写测试代码再编写实现代码的方式,可以更好地设计和组织代码结构。
- 更清晰的设计:测试代码促使开发者思考边界条件和异常情况,从而设计出更健壮的系统。
综上所述,编写测试代码不仅是保证软件质量的重要手段,还能提升开发效率、降低维护成本,并且有助于团队协作和产品的长期发展。