1、前言
软件测试是整个软件开发生命周期内的一个重要阶段,通常软件测试可以评估和验证软件系统的质量、可靠性、安全性和性能等方面。测试通过执行软件的一系列操作,旨在发现潜在的错误、缺陷或问题,从而确保软件能够按照预期工作。而软件测试往往覆盖了不同的层次和类型,其中单元测试是针对软件中最小的独立单元(通常是函数或方法)进行的测试。目标是确保每个单元独立地工作,并且对输入产生正确的输出。单元测试通常由开发人员编写,用于验证代码的正确性。
2、单元测试
单元测试是软件开发中的一种测试方法,用于验证代码中的最小单元(通常是函数或方法)是否按照预期工作。单元测试旨在隔离和测试软件的各个独立部分,确保每个部分的行为都是正确的。
Python 中,单元测试是通过使用 unittest 模块来实现的。unittest 模块提供了一个测试框架,允许开发者编写和执行测试用例,以确保代码的正确性。
3、unittest
unittest是python自带的用于单元测试的模块,类似java中的junit。
3.1、定义业务模块代码
首先我们先定义一个普通使用的模块代码math_operator.py,提供了一系列方法:
def add(x, y):
return x + y
def sub(x, y):
return x - y
def mul(x, y):
return x * y
def div(x, y):
if y == 0:
raise ValueError("Cannot divide by zero")
return x / y
3.2、定义单元测试代码
接着定义math_operator.py的单元测试代码math_operator_unittest.py:
import math_operator
import unittest
class TestMathOperator(unittest.TestCase):
def test_add(self):
print("我只是单元测试add方法")
self.assertEqual(math_operator.add(1, 2), 3) # 理论上这个单元测试通过
def test_sub(self):
print("我只是单元测试sub方法")
self.assertEqual(math_operator.sub(3, 2), 1) # 理论上这个单元测试通过
def test_mul(self):
print("我只是单元测试mul方法")
self.assertEqual(math_operator.mul(1, 2), 9) # 理论上这个单元测试不通过
def normal(self):
print("我只是个普通方法") # 他并不是单元测试函数,不会被执行
if __name__ == '__main__':
unittest.main()
需要几个步骤:
- 引入python自带的单元测试模块unittest:import unittest,同时也把我们要单元测试的业务模块引入:import math_operator。
- 定义单元测试类TestMathOperator,需要继承unittest.TestCase。
- 针对每个方法定义单元测试代码,以test_xxx()。只有以test_开头的方法才其中才会被认为是单元测试函数,不然是普通函数,在执行单元测试套件时候是不会被执行的。
- 单元测试代码这里使用了断言assert,他会在判断成功后返回true,否则异常。
- 运行单元测试,unittest.main()。
看下运行结果:
可以看出,首先只有test_xxx()的方法被执行,一个普通的函数在里面是不会被执行的。其次,看终端提示的倒数第三行,显示运行了3个test case,并花费了0.004s。倒数第二行显示整个的单元测试套件结果是FAILED(不通过),不通过数量1个(failures=1)。再往上看,可以看出再执行mul方法时,期望结果是2(Expected: 2),但是实际结果却是9(Actual: 9)。实际结果与期望结果不符,因此测试不通过。
当我们手动修改一下test_mul()方法:
def test_mul(self):
print("我只是单元测试mul方法")
self.assertEqual(math_operator.mul(1, 2), 2)
再此运行,发现测试用例通过。顶部提示Tests passed: 3。
3.3、执行单元测试方式
执行单元测试方式有多种,上面代码中,在某个单元测试类中直接加上入口函数,这种也是最简单的。
if __name__ == '__main__':
unittest.main()
还有一种方式是通过命令行执行:
python -m unittest math_operator_unittest.py
运行结果:
4、setUp & tearDown
前面我们已经能够编写一些简单的单元测试函数了,但是往往项目中除了对某个工具方法进行单元测试以外,还需要对一些服务进行测试,比如这些服务是需要连接数据库的,这时候就要求我们单元测试代码中具备连接数据库以及关闭数据库的能力。这时候就需要用到setUp和tearDown了。
在 Python 的 unittest 框架中,setUp 和 tearDown 方法是用于设置和清理测试环境的两个重要的特殊方法。这两个方法在每个测试方法执行前后分别被调用,以确保测试环境的准备和清理。
- setUp:在每个测试方法执行之前调用。通常用于准备测试环境,例如初始化变量、建立测试数据等,或在测试之前创建对象或设置必要的资源。
- tearDown :每个测试方法执行之后调用。通常用于清理测试环境,例如关闭文件、释放资源等,或在测试之后进行清理操作。
我们用上面的单元测试代码添加下setUp和tearDown方法:
import math_operator
import unittest
class TestMathOperator(unittest.TestCase):
def setUp(self) -> None:
print("setUp()单元测试前执行,这里开启数据库连接")
def tearDown(self) -> None:
print("tearDown()单元测试后执行,这里关闭数据库连接")
def test_add(self):
print("我只是单元测试add方法")
self.assertEqual(math_operator.add(1, 2), 3)
def test_sub(self):
print("我只是单元测试sub方法")
self.assertEqual(math_operator.sub(3, 2), 1)
def test_mul(self):
print("我只是单元测试mul方法")
self.assertEqual(math_operator.mul(1, 2), 2)
def normal(self):
print("我只是个普通方法")
if __name__ == '__main__':
unittest.main()
运行一下,可以发现每个单元测试用例前后分别被setUp和tearDown包围,这个其实就是他们两的生命周期。
备注:
上面的def setUp(self) -> None: 是使用类型注解(Type Hinting)的 setUp 方法的声明。类型注解是 Python 3.5 及更高版本引入的特性,用于提供变量和函数参数的类型信息。在这个声明中,self 表示当前对象实例,而 -> None 表示该方法的返回类型是 None。这并不是强制性的,因为在 Python 中,函数和方法默认的返回类型是 None。而setUp 方法没有返回值(返回 None),因此使用 -> None 是一种良好的注释习惯,让读者知道这个方法的返回类型是 None。
5、小结
单元测试作为软件开发活动中的一项重要实践,有很多好处。不仅能提前发现一些潜在问题,确保代码正确性,减少回归问题。Bug往往越早期发现,修复成本越低,因而可以降低维护的成本。所以在日常开发活动中,还是要养成编写单元测试代码的习惯,通过建立一套健全的单元测试,开发者可以更自信地进行代码更改,并确保所做的修改不会对系统的整体功能产生负面影响。
版权归原作者 有一只柴犬 所有, 如有侵权,请联系我们删除。