使用Nose+unittest进行Python模块自动化测试实战+源代码
直接通过Python解释器执行测试文件,会完整触发文件末尾这种方法的最终输出是和test_module.py的main函数内设置有关的。
在Python开发中,自动化测试是保障代码质量的关键环节。Nose(nosetests)是基于Python标准库unittest扩展的测试框架,它简化了测试用例的发现和执行流程,是Python自动化测试的常用工具。本文将通过一个包含数学运算和网络操作的实战案例,带你掌握Nose的基本使用方法。
一、环境准备:安装Nose
首先需要安装Nose框架,使用pip命令即可快速完成安装:
pip install nose
二、项目结构规划
Nose遵循「约定优于配置」的原则,测试文件和被测试文件需遵循一定的命名规范:
- 被测试文件:例如
module.py(实现核心功能) - 测试文件:命名为
test_被测试文件名.py(例如test_module.py)
推荐的项目目录结构:
your_project/
└── tests/ # 测试目录
└── test_module.py # 测试用例文件
└── module.py # # 被测试模块(核心功能)
三、编写被测试模块(tests/module.py)
这里实现一个包含基础数学运算和网络操作的模块,作为测试对象:
后面如果需要按照自己的想法修改的话,只需要模仿着写就好
"""
简单功能模块
包含基础数学运算和网页访问相关功能
"""
def add(a, b):
"""
计算两个数的和
Args:
a: 第一个数(int/float)
b: 第二个数(int/float)
Returns:
两数之和(int/float)
"""
return a + b
def subtract(a, b):
"""
计算两个数的差
Args:
a: 被减数(int/float)
b: 减数(int/float)
Returns:
两数之差(int/float)
"""
return a - b
def multiply(a, b):
"""
计算两个数的积
Args:
a: 第一个数(int/float)
b: 第二个数(int/float)
Returns:
两数之积(int/float)
"""
return a * b
def divide(a, b):
"""
计算两个数的商
Args:
a: 被除数(int/float)
b: 除数(int/float)
Returns:
两数之商(float)
Raises:
ZeroDivisionError: 当除数为0时抛出异常
"""
if b == 0:
raise ZeroDivisionError("除数不能为零")
return a / b
def check_website_access(url):
"""
检查指定网址是否可访问(HTTP 200响应)
Args:
url: 待检查的网址(str)
Returns:
bool: True=可访问,False=不可访问
"""
import requests
try:
response = requests.get(url, timeout=10)
return response.status_code == 200
except Exception:
return False
def open_browser_and_navigate(url):
"""
调用系统默认浏览器打开指定网址
Args:
url: 待访问的网址(str)
Returns:
bool: True=操作成功,False=操作失败
"""
try:
import webbrowser
webbrowser.open(url)
return True
except Exception:
return False
# 预定义测试数据
TEST_CASES = [
(1, 2, 3), # 1 + 2 = 3
(5, -3, 2), # 5 + (-3) = 2
(0, 5, 5), # 0 + 5 = 5
]
WEBSITE_TEST_URLS = [
"https://www.baidu.com",
"https://www.google.com",
"https://www.github.com"
]
if __name__ == "__main__":
# 模块自测(快速验证核心功能)
print("=== 数学运算自测 ===")
for a, b, expected in TEST_CASES:
result = add(a, b)
print(f"{a} + {b} = {result} ({'✓' if result == expected else '✗'})")
print("\n=== 网站访问自测 ===")
for url in WEBSITE_TEST_URLS[:2]: # 仅测试前2个网址
accessible = check_website_access(url)
print(f"{url}: {'可访问' if accessible else '无法访问'}")
四、编写测试用例(tests/test_module.py)
基于前面的module.py编写,覆盖被测试模块的所有核心功能,包括单元测试和集成测试,同样如果需要修改,仅需要模仿写就好,可以看到这里函数的调用实际上是使用了module里面的功能的
"""
module.py 功能测试用例
覆盖数学运算、网络操作、测试数据验证、集成场景
"""
import unittest
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from module import (
add, subtract, multiply, divide,
check_website_access, open_browser_and_navigate,
TEST_CASES, WEBSITE_TEST_URLS
)
class TestMathOperations(unittest.TestCase):
"""数学运算功能单元测试"""
def test_add_basic(self):
"""测试加法基础场景(整数、负数、零)"""
self.assertEqual(add(1, 2), 3)
self.assertEqual(add(0, 0), 0)
self.assertEqual(add(-1, 1), 0)
self.assertEqual(add(-5, -3), -8)
def test_subtract_basic(self):
"""测试减法基础场景"""
self.assertEqual(subtract(5, 3), 2)
self.assertEqual(subtract(0, 5), -5)
self.assertEqual(subtract(-1, -2), 1)
self.assertEqual(subtract(10, 0), 10)
def test_multiply_basic(self):
"""测试乘法基础场景"""
self.assertEqual(multiply(3, 4), 12)
self.assertEqual(multiply(0, 5), 0)
self.assertEqual(multiply(-2, 3), -6)
self.assertEqual(multiply(-4, -5), 20)
def test_divide_basic(self):
"""测试除法基础场景"""
self.assertEqual(divide(10, 2), 5)
self.assertEqual(divide(0, 5), 0)
self.assertEqual(divide(-15, 3), -5)
self.assertEqual(divide(-20, -4), 5)
def test_divide_by_zero(self):
"""测试除数为零的异常场景"""
with self.assertRaises(ZeroDivisionError):
divide(5, 0)
with self.assertRaises(ZeroDivisionError):
divide(0, 0)
def test_float_operations(self):
"""测试浮点数运算(解决精度问题)"""
self.assertAlmostEqual(add(1.1, 2.2), 3.3, places=1)
self.assertAlmostEqual(subtract(5.5, 2.2), 3.3, places=1)
self.assertAlmostEqual(multiply(2.5, 4.0), 10.0, places=1)
self.assertAlmostEqual(divide(7.5, 2.5), 3.0, places=1)
class TestWebOperations(unittest.TestCase):
"""网络操作功能单元测试"""
def test_check_website_access_valid(self):
"""测试有效网址的访问检查"""
accessible_sites = [
"https://httpbin.org/get", # 测试专用接口(稳定性高)
"https://www.baidu.com"
]
for site in accessible_sites:
result = check_website_access(site)
self.assertIsInstance(result, bool, f"网站 {site} 检查结果应为布尔值")
def test_check_website_access_invalid(self):
"""测试无效网址的访问检查"""
invalid_sites = [
"https://this-site-definitely-does-not-exist-12345.com",
"https://invalid-domain-name-that-cannot-be-resolved.com"
]
for site in invalid_sites:
self.assertFalse(check_website_access(site), f"无效网址 {site} 应返回False")
def test_open_browser_basic(self):
"""测试浏览器打开功能"""
test_urls = ["https://www.baidu.com", "https://chat.deepseek.com/"]
for url in test_urls:
self.assertTrue(open_browser_and_navigate(url), f"打开URL {url} 失败")
class TestTestData(unittest.TestCase):
"""测试数据格式验证(保障测试数据有效性)"""
def test_test_cases_format(self):
"""验证数学测试用例的格式"""
self.assertIsInstance(TEST_CASES, list)
self.assertGreater(len(TEST_CASES), 0)
for case in TEST_CASES:
self.assertIsInstance(case, tuple)
self.assertEqual(len(case), 3) # 格式:(a, b, expected_result)
a, b, expected = case
self.assertIsInstance(a, (int, float))
self.assertIsInstance(b, (int, float))
self.assertIsInstance(expected, (int, float))
def test_website_urls_format(self):
"""验证网址测试数据的格式"""
self.assertIsInstance(WEBSITE_TEST_URLS, list)
self.assertGreater(len(WEBSITE_TEST_URLS), 0)
for url in WEBSITE_TEST_URLS:
self.assertIsInstance(url, str)
self.assertTrue(url.startswith(('http://', 'https://')),
f"URL {url} 应以http/https开头")
class TestIntegration(unittest.TestCase):
"""集成测试(验证功能间的协作逻辑)"""
def test_math_operations_consistency(self):
"""验证数学运算的逻辑一致性(加法↔减法、乘法↔除法)"""
# 加法与减法的反向验证
a, b = 10, 3
sum_result = add(a, b)
self.assertEqual(subtract(sum_result, b), a)
# 乘法与除法的反向验证
a, b = 15, 3
mul_result = multiply(a, b)
self.assertEqual(divide(mul_result, b), a)
def test_using_test_data(self):
"""使用预定义测试数据验证加法功能"""
for a, b, expected in TEST_CASES:
self.assertEqual(add(a, b), expected,
f"add({a}, {b}) 预期{expected},实际{add(a, b)}")
if __name__ == '__main__':
# 构建测试套件并运行
loader = unittest.TestLoader()
suite = unittest.TestSuite()
# 添加所有测试类
suite.addTests(loader.loadTestsFromTestCase(TestMathOperations))
suite.addTests(loader.loadTestsFromTestCase(TestWebOperations))
suite.addTests(loader.loadTestsFromTestCase(TestTestData))
suite.addTests(loader.loadTestsFromTestCase(TestIntegration))
# 运行测试(详细输出模式)
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
# 输出测试统计结果
print(f"\n=== 测试结果统计 ===")
print(f"总测试用例数: {result.testsRun}")
print(f"失败数: {len(result.failures)}")
print(f"错误数: {len(result.errors)}")
success_rate = (result.testsRun - len(result.failures) - len(result.errors)) / result.testsRun * 100
print(f"测试成功率: {success_rate:.1f}%")
五、运行测试
下面有六种可行的方法,前四种博主亲测可行,后两种博主还在尝试ing,试出来就更新一下
方式1:直接运行测试文件(带自定义统计输出)
直接通过Python解释器执行测试文件,会完整触发文件末尾if __name__ == '__main__':中的自定义逻辑(包括测试结果统计、打印等):
python tests/test_module.py

这种方法的最终输出是和test_module.py的main函数内设置有关的
方式2:使用Nose自动发现并运行测试(经典第三方方式)(理论可行没成功)
Nose框架会自动扫描指定目录下符合test_*.py命名规范的测试文件,无需手动构建测试套件:
# 自动查找tests目录下所有测试用例,-v开启详细输出
nosetests -v tests/
核心参数:
-v:详细输出模式(显示每个测试用例的执行结果);-s:显示print输出(默认Nose会屏蔽print语句);--with-coverage:生成代码覆盖率报告(需提前安装coverage:pip install coverage)。
特点:适配“约定优于配置”的测试理念,是自动化脚本中常用的经典方式。
方式3:使用unittest内置discover(纯原生Python方式)
Python标准库unittest自带的discover功能,无需安装任何第三方库,可自动发现并运行测试,但不会执行测试文件中main函数的自定义逻辑:
# 自动发现tests目录下的测试用例,-v为详细输出
python -m unittest discover tests -v
效果示例:
特点:纯原生无依赖,输出格式更简洁(仅展示核心测试结果),不会返回test_module.py的main函数中自定义的统计内容,适合对环境依赖要求严格的场景。
方式4:右键选择nosetest方式运行
这个结果就相对更简洁一些
方式5:使用Nose自动发现并运行测试(经典第三方方式)(理论可行没尝试成功)
Nose框架会自动扫描指定目录下符合test_*.py命名规范的测试文件,无需手动构建测试套件:
# 自动查找tests目录下所有测试用例,-v开启详细输出
nosetests -v tests/
核心参数:
-v:详细输出模式(显示每个测试用例的执行结果);-s:显示print输出(默认Nose会屏蔽print语句);--with-coverage:生成代码覆盖率报告(需提前安装coverage:pip install coverage)。
特点:适配“约定优于配置”的测试理念,是自动化脚本中常用的经典方式。
方式6:使用nose2(Nose的现代化升级版)(理论可行没尝试成功)
nose2是Nose的官方升级版,兼容unittest语法且功能更丰富,使用方式更轻量化:
第一步:安装nose2
pip install nose2
第二步:运行测试
# 自动发现并运行tests目录下的所有测试用例
nose2 -v tests/
特点:
- 完全兼容原有
unittest测试用例,无需修改一行代码; - 支持更多扩展插件(如并行测试、XML报告生成、测试过滤);
- 输出格式优化,异常详情展示更清晰,默认集成实用功能(如用例执行时长统计)。
更多推荐


所有评论(0)