在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:生成代码覆盖率报告(需提前安装coveragepip install coverage)。
    特点:适配“约定优于配置”的测试理念,是自动化脚本中常用的经典方式。

方式3:使用unittest内置discover(纯原生Python方式)

Python标准库unittest自带的discover功能,无需安装任何第三方库,可自动发现并运行测试,但不会执行测试文件中main函数的自定义逻辑

# 自动发现tests目录下的测试用例,-v为详细输出
python -m unittest discover tests -v

效果示例
测试输出示例
特点:纯原生无依赖,输出格式更简洁(仅展示核心测试结果),不会返回test_module.pymain函数中自定义的统计内容,适合对环境依赖要求严格的场景。

方式4:右键选择nosetest方式运行在这里插入图片描述

这个结果就相对更简洁一些
在这里插入图片描述

方式5:使用Nose自动发现并运行测试(经典第三方方式)(理论可行没尝试成功)

Nose框架会自动扫描指定目录下符合test_*.py命名规范的测试文件,无需手动构建测试套件:

# 自动查找tests目录下所有测试用例,-v开启详细输出
nosetests -v tests/

核心参数

  • -v:详细输出模式(显示每个测试用例的执行结果);
  • -s:显示print输出(默认Nose会屏蔽print语句);
  • --with-coverage:生成代码覆盖率报告(需提前安装coveragepip install coverage)。
    特点:适配“约定优于配置”的测试理念,是自动化脚本中常用的经典方式。

方式6:使用nose2(Nose的现代化升级版)(理论可行没尝试成功)

nose2是Nose的官方升级版,兼容unittest语法且功能更丰富,使用方式更轻量化:

第一步:安装nose2
pip install nose2
第二步:运行测试
# 自动发现并运行tests目录下的所有测试用例
nose2 -v tests/

特点

  • 完全兼容原有unittest测试用例,无需修改一行代码;
  • 支持更多扩展插件(如并行测试、XML报告生成、测试过滤);
  • 输出格式优化,异常详情展示更清晰,默认集成实用功能(如用例执行时长统计)。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐