简单的 Python 单元测试:设置、拆卸、夹具等

894次阅读
没有评论

共计 14344 个字符,预计需要花费 36 分钟才能阅读完成。

简单的 Python 单元测试:设置、拆卸、夹具等

Python 是一种多功能且非常流行的编程语言,广泛用于各种应用程序,例如 Web 开发、数据分析、机器学习等。 然而,经验丰富的开发人员明白,代码创建只是该过程的一方面。 确保代码在不同条件下正确运行同样重要,甚至更重要。 这就是 Python 单元测试框架发挥重要作用的地方。

单元测试是 Python 软件开发过程的重要组成部分。 这些测试旨在确定源代码离散单元(如函数或方法)的正确性。 通过单独验证软件的每个部分,您可以防止在更改或添加代码时无意中引入错误。 例如,如果您正在开发一个应用程序来使用 Python 脚本和 Boto3 库 自动化基础设施部署,以便在没有单元测试的情况下与 AWS 交互,则验证您的代码是否按预期运行将涉及每次部署基础设施。 这个过程可能非常耗时、成本高昂并且存在潜在风险。

然而,Python 的单元测试允许模拟 Boto3 的行为,无需实际的 AWS 交互即可测试脚本。 这有助于在脚本中的任何错误或逻辑错误影响您的基础设施之前捕获并纠正它们。 Python的内置模块 unittest提供了一种创建和组织测试用例的系统方法,提供了广泛的功能来高效地测试您的 Python 代码。 接下来的部分将更深入地探讨设置和拆除测试、使用夹具、断言条件等的细节。 借助从本指南中获得的知识,您将能够顺利掌握 Python 单元测试并编写健壮、无错误的 Python 代码。

因此,请继续关注我们踏上成为 Python 单元测试专家的旅程!

目录

Python 单元测试的基础知识

在我们深入研究 Python 单元测试的更复杂方面之前,掌握基础知识至关重要。 本节重点介绍 Python 单元测试的三个基本元素:设置、拆卸和固定装置。

Python 单元测试设置

在单元测试中,“设置”是指运行一个测试或一组测试之前的准备工作。 蟒蛇的 unittest模块提供了一个 setUp()方法,您可以在其中编写准备代码。 每次测试前都会自动调用此方法。

Consider the following example:

import unittest
class TestMyFunction(unittest.TestCase):
    def setUp(self):
        self.test_list = [1, 2, 3, 4, 5]
    def test_length_of_list(self):
        self.assertEqual(len(self.test_list), 5)

在这里,我们正在测试一个返回列表长度的简单函数。 setUp() 方法在每次测试运行之前初始化列表。 因此,即使测试修改了列表,setUp() 方法也会确保每个测试都从原始列表开始。

Python 单元测试拆解

与单元测试中的设置过程相对应的是“拆卸”过程。 中的tearDown()方法 unittest模块是您清理设置的所有资源的地方。 每次测试后都会自动调用该方法。

继续我们之前的例子:

import unittest
class TestMyFunction(unittest.TestCase):
    def setUp(self):
        self.test_list = [1, 2, 3, 4, 5]
    def test_length_of_list(self):
        self.assertEqual(len(self.test_list), 5)
    def tearDown(self):
        del self.test_list

在这种情况下,tearDown() 方法会在每次测试后删除列表。 在此示例中,这并不是绝对必要的,但当您的测试使用文件句柄或数据库连接等资源时,它就变得至关重要。

了解 Python 单元测试装置

固定装置是用作运行测试的基线的固定系统状态。 在 Python 中 unittest模块,该 setUp()和 tearDown()方法一起形成测试装置。 这可确保每个测试在一致的环境中运行,且与其他测试隔离。

蟒蛇的 unittest还通过提供支持类级别的设置和拆卸 setUpClass()和 tearDownClass()方法。 这些方法对整个类而不是每个测试调用一次。

这是一个例子:

import unittest
class TestMyFunction(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.test_list = [1, 2, 3, 4, 5]
    def test_length_of_list(self):
        self.assertEqual(len(self.test_list), 5)
    @classmethod
    def tearDownClass(cls):
        del cls.test_list

In this case, the test_list is set up once for the entire class and torn down after all the tests have run. This is useful for expensive operations, like establishing a database connection, that you want to perform only once.

Mastering the setup and teardown processes and understanding fixtures are fundamental to effective Python unit testing. As we progress, you will see how these components are building blocks for more advanced testing techniques.

Mastering Python Unit Test Assertions

断言是任何测试框架的核心。 在 Python 中 unittest模块中,断言方法可用于检查等效性、真实性、是否引发异常以及许多其他条件。 如果断言失败,则测试失败,框架将继续进行下一个测试。

在 Python 单元测试中断言 True

这 assertTrue()方法是Python中常用的断言 unittest框架。 它测试给定的条件或表达式是否是 True.

这是一个简单的例子:

import unittest
class TestMyFunction(unittest.TestCase):
    def test_is_even(self):
        for i in [2, 4, 6, 8, 10]:
            self.assertTrue(i % 2 == 0)

在这个例子中,测试 test_is_even检查数字列表是否仅包含偶数。 如果列表中的任何数字不为偶数, assertTrue()会失败,导致测试失败。

在测试中捕获和引发异常

在现实场景中,您的代码可能会引发异常。 蟒蛇的 unittest测试 Python异常的方法。 模块提供了一种使用assertRaises()方法

Here’s an example:

import unittest
def division(dividend, divisor):
    return dividend / divisor
class TestMyFunction(unittest.TestCase):
    def test_division(self):
        with self.assertRaises(ZeroDivisionError):
            division(10, 0)

在此示例中, test_division测试检查是否 division函数提出了一个 ZeroDivisionError当除数为零时。 如果没有,则 assertRaises()方法将失败,导致测试失败。

有时,您想要测试代码的特定部分(而不是测试中的任何地方)是否引发异常。 为此,您可以使用 assertRaises()方法作为上下文管理器,如上面的示例所示。

此外 assertRaises(), 这 unittest模块提供了其他几种断言方法,例如 assertEqual()assertIn()assertIsNone()等等,以涵盖您可以在测试中检查的各种条件。

这些断言技术对于 Python 单元测试至关重要,可让您确保代码在不同条件下的行为符合预期。 掌握这些方法将显着提高您编写有效单元测试的能力。

Python 单元测试模拟技术

模拟是单元测试不可或缺的一部分。 它允许您替换被测系统的某些部分并断言它们的使用方式。 这 unittest.mockPython 中的模块提供了一种灵活的方法来模拟 对象 、 函数 、方法和代码的其他方面。 让我们探讨如何在 Python 单元测试中模拟类和函数。

Python 单元测试中的模拟类

测试时,您可能会发现自己处于必须将部分代码与系统其余部分隔离的情况。 在这些情况下,模拟类非常有用。

例如,考虑一个发出 HTTP 请求的类:

import requests
class HttpClient:
    def get_data(self, url):
        response = requests.get(url)
        return response.json()

测试 get_data该方法可能很棘手,因为它依赖于外部系统(互联网)。 但是,您可以使用 unittest.mock模块的 Mock或者 MagicMock类来替换 requests.get带有模拟的方法。

您可以这样做:

import unittest
from unittest.mock import patch
from my_module import HttpClient
class TestHttpClient(unittest.TestCase):
    @patch('requests.get')
    def test_get_data(self, mock_get):
        mock_response = mock_get.return_value
        mock_response.json.return_value = {'key': 'value'}
        client = HttpClient()
        data = client.get_data('http://fakeurl.com')
        self.assertEqual(data, {'key': 'value'})
        mock_get.assert_called_once_with('http://fakeurl.com')

在此示例中, patch装饰器替换 requests.get在测试上下文中进行模拟。 这 mock_get测试方法的参数是这个模拟对象。

Python 单元测试中的模拟函数

同样,您可能想要隔离一个函数以进行测试。 在这种情况下,您可以用模拟替换该函数。

假设您有一个调用另一个函数的函数,并且您想要单独测试它:

def add_one(number):
    return number + 1
def add_two(number):
    return add_one(add_one(number))

您可以更换 add_one测试时使用模拟 add_two像这样:

import unittest
from unittest.mock import patch
from my_module import add_two
class TestMyFunction(unittest.TestCase):
    @patch('my_module.add_one')
    def test_add_two(self, mock_add_one):
        mock_add_one.side_effect = [2, 3]
        result = add_two(1)
        self.assertEqual(result, 3)
        self.assertEqual(mock_add_one.call_count, 2)

In this example, the patch decorator replaces add_one with a mock in the context of the test. The mock_add_one argument to the test method is this mock object.

These mocking techniques can significantly simplify your unit tests by isolating the system under test. However, mocking should be used judiciously, as over-mocking can make your tests harder to understand and maintain.

高级 Python 单元测试概念

现在我们已经介绍了 Python 单元测试的基础知识,让我们深入研究一些在特定场景中有用的高级概念。 我们将讨论命令行参数、环境变量和使用技术 print在单元测试中。

单元测试中的命令行参数

在某些情况下,您的代码可能依赖于命令行参数。 这在编写单元测试时可能会带来挑战,但是 Python 的 unittest.mock.patch函数来拯救。

考虑一个使用命令行参数的简单函数:

import sys
def add_command_line_arguments():
    return sum(int(arg) for arg in sys.argv[1:])

我们可以通过mock来测试这个函数 sys.argv:

import unittest
from unittest.mock import patch
class TestCommandLine(unittest.TestCase):
    @patch('sys.argv', ['file', '1', '2', '3'])
    def test_add_command_line_arguments(self):
        from my_module import add_command_line_arguments
        result = add_command_line_arguments()
        self.assertEqual(result, 6)

Setting up Environment Variables in Tests

Environment variables are a common means of configuring applications. You might want to set up specific environment variables when writing unit tests for such applications.

Consider a simple function that reads an environment variable:

import os
def get_database_url():
    return os.getenv('DATABASE_URL')

You can test this function by mocking os.getenv:

import unittest
from unittest.mock import patch
class TestEnvironmentVariables(unittest.TestCase):
    @patch.dict('os.environ', {'DATABASE_URL': 'postgres://localhost/testdb'})
    def test_get_database_url(self):
        from my_module import get_database_url
        result = get_database_url()
        self.assertEqual(result, 'postgres://localhost/testdb')

在本例中,我们使用了 patch.dict to temporarily set the DATABASE_URL environment variable for the duration of the test.

Python 单元测试打印技术

有时,您可能需要使用以下命令测试输出到控制台的代码 print功能。 为此,您可以重定向 sys.stdout到字符串缓冲区,然后将缓冲区的内容与预期输出进行比较。

这是一个例子:

def greet(name):
    print(f'Hello, {name}!')
class TestPrint(unittest.TestCase):
    def test_greet(self):
        import io
        import sys
        from my_module import greet
        captured_output = io.StringIO()
        sys.stdout = captured_output
        greet('Alice')
        sys.stdout = sys.__stdout__
        self.assertEqual(captured_output.getvalue(), 'Hello, Alice!\n')

在这个例子中, sys.stdout暂时重定向到 StringIO目的。 打电话后 greet('Alice'),输出 print函数进入这个对象。 这 sys.stdout然后恢复,并将捕获的输出与预期输出进行比较。

这些高级测试概念可以增加您的测试能力的深度,使您能够在单元测试中涵盖更复杂的场景。 与往常一样,请注意这些技术可能会给您的测试带来的潜在副作用和复杂性。

Python:真实世界的单元测试示例

让我们通过深入研究一些真实的 Python 单元测试示例,将迄今为止学到的理论、技术和概念付诸实践。 我们将检查 Python 类和 Python 函数 的单元测试。

测试 Python 类

考虑一个简单的 Python 类 Calculator,它执行基本算术运算:

class Calculator:
    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):
        if b == 0:
            raise ValueError("Can't divide by zero.")
        return a / b

您可以为此类编写单元测试,如下所示:

import unittest
from my_module import Calculator
class TestCalculator(unittest.TestCase):
    def setUp(self):
        self.calc = Calculator()
    def test_add(self):
        result = self.calc.add(10, 5)
        self.assertEqual(result, 15)
    def test_subtract(self):
        result = self.calc.subtract(10, 5)
        self.assertEqual(result, 5)
    def test_multiply(self):
        result = self.calc.multiply(10, 5)
        self.assertEqual(result, 50)
    def test_divide(self):
        result = self.calc.divide(10, 2)
        self.assertEqual(result, 5)
    def test_divide_by_zero(self):
        with self.assertRaises(ValueError):
            self.calc.divide(10, 0)

Testing a Python Function

Let’s say we have a Python function that fetches data from an API:

import requests
def get_posts():
    response = requests.get('https://jsonplaceholder.typicode.com/posts')
    if response.status_code != 200:
        raise Exception("API request failed!")
    return response.json()

我们可以通过模拟来测试这个函数 requests.get方法:

import unittest
from unittest.mock import patch
from my_module import get_posts
class TestGetPosts(unittest.TestCase):
    @patch('requests.get')
    def test_get_posts(self, mock_get):
        mock_response = mock_get.return_value
        mock_response.status_code = 200
        mock_response.json.return_value = [{'id': 1, 'title': 'Test post'}]
        posts = get_posts()
        self.assertEqual(posts, [{'id': 1, 'title': 'Test post'}])
        mock_get.assert_called_once_with('https://jsonplaceholder.typicode.com/posts')

这样,我们就不需要在测试期间发出实际的 HTTP 请求,从而提高了测试的速度和可靠性。

将这些单元测试实践应用到您的实际项目中将帮助您保持较高的代码质量并减少将错误引入应用程序的机会。 永远记住,经过充分测试的应用程序是可靠且可维护的应用程序。

有关 Python 单元测试的更深入信息,请查看我们的 Python Boto3 课程 、 使用 LocalStack 测试 Python AWS 应用程序 和 AWS Lambda Python 单元测试 – 完整教程 文章。

了解 Python 单元测试报告

运行 Python 单元测试后,了解结果以及如何解释它们至关重要。 测试运行的输出是一份报告,其中提供了有关哪些测试通过、哪些测试失败以及失败原因的见解。 它是维护代码库健康和质量的重要工具。

这是运行的典型输出 unittest从命令行:

$ python -m unittest tests.test_module
....F..
======================================================================
FAIL: test_division (__main__.TestMyFunction)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests/test_module.py", line 15, in test_division
    self.assertEqual(result, 5)
AssertionError: 4.0 != 5
----------------------------------------------------------------------
Ran 7 tests in 0.002s
FAILED (failures=1)

让我们来分解一下:

  • ....F..:该线代表单独测试的结果。 每个 .表示通过测试, F表示测试失败, E表示测试中的错误(意外的异常),并且 s代表跳过的测试。
  • FAIL: test_division (__main__.TestMyFunction):这一行表示失败的测试方法和类。
  • Traceback (most recent call last):...:这是失败测试的回溯。 它显示导致断言失败的执行路径,这可以帮助查明代码中的问题。
  • Ran 7 tests in 0.002s:此行显示运行的测试总数以及运行这些测试所需的时间。
  • FAILED (failures=1):该线代表最终的测试结果。 如果所有测试都通过,它会说 OK。 如果任何测试失败或错误,它会说 FAILED,然后是失败和错误的计数。

对于较大的测试套件,您可能需要使用更高级的工具来生成详细的 HTML 报告,例如 unittest-xml-reporting或者 pytest-html。 这些工具提供更深入的报告,通常包括按类或模块分组、搜索、过滤等功能。

了解测试报告是测试过程中至关重要的一部分。 它允许您快速识别哪些测试失败以及失败的原因,这是诊断和修复代码中的问题的第一步。 始终关注您的测试报告并及时解决故障或错误,以保持代码库的质量。

Python 单元测试最佳实践

单元测试是任何良好的软件开发实践的一个组成部分。 它是一个强大的工具,可以确保各个代码组件的正确性,从而形成健壮且可靠的应用程序。 但是,您必须遵循一些最佳实践才能从单元测试中获取最大价值。 以下是 Python 单元测试的一些指南。

Keep Your Tests Short and Simple

理想情况下,每个测试都应该集中于代码功能的一个方面。 如果测试失败,您立即知道代码的哪一部分有问题。 保持测试简短还可以让其他开发人员更容易理解每​​个测试在做什么,这在团队中工作时尤其重要。

使用描述性测试方法名称

测试名称应该清楚地说明它正在测试什么。 如果测试失败,其名称应该可以很好地说明出了什么问题,甚至无需查看测试代码。 一个好的做法是使用类似的模式 test_<method>_when_<condition>_then_<result>,它传达了测试的意图。

不要忽视失败的测试

如果测试失败,则意味着出现问题。 不要忽略失败的测试或将其注释掉。 相反,调查失败的原因并解决问题。 忽略失败的测试可能会掩盖可能导致更大问题的问题。

明智地使用模拟和存根

模拟和存根对于隔离被测系统非常有用,但如果使用不当,它们也会使测试变得更加复杂和难以理解。 使用模拟和存根时要小心。 确保你没有过度模拟,这可能会导致脆弱的测试因错误的原因而失败。

为快乐和悲伤的路径编写测试

Don’t just test the “happy path” (the ideal scenario where everything works correctly). Also, test the negative cases where things might go wrong. For example, test how your code handles invalid inputs, network errors, etc. These tests can help ensure your application behaves gracefully even when things go wrong.

Refactor Your Tests Regularly

Tests are code too, and they can and should be refactored. If your tests are becoming too complicated or have a lot of duplication, take the time to refactor them. Refactoring can make your tests easier to understand and maintain.

These best practices will help you write better tests, which will, in turn, help you create more reliable and maintainable Python applications. Remember, testing aims not just to find bugs but to create a robust and resilient system that you can confidently develop and evolve.

FAQ

What are Python unit tests?

Python unit tests are an essential part of the software development process designed to validate the correctness of individual units of source code, such as functions or methods. These tests ascertain that a program’s distinct code behaves as intended. Python provides a built-in module known as unittest for creating these tests, offering a systematic way to build and organize test cases, set up and tear down test environments, and more. Using Python unit tests ensures that any changes or additions to the code don’t inadvertently introduce errors, contributing to the development of robust, error-free software.

pytest vs unittest

Pytest and unittest are two popular testing frameworks in Python. Unittest, a built-in Python module, follows the xUnit testing style and requires tests to be put into classes as methods. It supports setup and teardown methods for test cases and modules. Pytest, on the other hand, is a third-party module that supports a simpler, function-based approach to writing tests. It also supports fixtures, a powerful alternative to setup and teardown methods. Pytest can run unittest test cases, which makes transitioning between the two frameworks easier. Both are powerful tools for writing and executing tests in Python.

What are the 4 types of testing in Python 3?

Python 3 中的四种主要测试类型是单元测试、集成测试、功能测试和回归测试。 单元测试 侧重于验证软件各个组件的功能。 集成测试 测试应用程序不同模块之间的相互依赖性。 功能测试 确保软件的行为符合指定的要求。 对软件进行更改后,回归测试会检查现有功能中是否存在新错误或回归。 Python的标准库提供了 unittest用于单元测试和第三方库的模块,例如 pytest和 nose可用于其他类型的测试。

Python 支持单元测试吗?

Python 通过其内置模块原生支持单元测试, unittest。 这 unittest受 xUnit 架构启发的模块提供了一套全面的工具来创建和运行测试,确保源代码的各个单元(如函数或方法)按预期运行。 这包括对测试自动化的支持、共享测试的设置和关闭代码、将测试聚合到集合中以及测试与报告框架的独立性。 此外,还可以使用第三方库,例如 pytest提供编写和执行测试的替代方法,扩展了 Python 的单元测试功能。

如何在 Python 中执行单元测试?

要在 Python 中执行单元测试,您主要使用 unittestmodule,一个内置的Python库。 从导入开始 unittest并通过子类化创建一个新的测试用例 unittest.TestCase。 此类定义以“test”一词开头的方法来表示各个单元测试。 每个测试通常涉及调用一个函数并使用断言方法将实际结果与预期结果进行比较,例如 assertEqual()assertTrue()等。定义测试后,可以通过调用来运行它们 unittest.main()或使用命令行界面,例如 python -m unittest后面是测试脚本名称。 每个单元测试都应该测试代码的特定方面,以确保其正确性。

什么是 Python 单元测试?

Python单元测试是指系统地验证Python源代码中各个单元(例如函数或方法)的正确性。 它涉及编写测试用例来验证每个单元是否按预期运行,确保对代码库的更改或添加不会引入错误。 Python 的内置 unittest模块提供了一个用于创建和组织这些测试的框架,提供测试设置、执行和结果断言的功能。 单元测试可以让开发者及早发现bug,提高代码质量,增强Python应用程序的整体可靠性和可维护性。

结论:利用 Python 单元测试文档

单元测试是软件开发的一个重要方面,掌握它可以显着提高 Python 应用程序的可靠性和可维护性。 我们讨论并演示了与 Python 单元测试相关的各种概念、技术和最佳实践。 然而,测试世界还有很多东西需要探索,官方的 Python 单元测试文档是您持续学习之旅中可以利用的重要资源。

是 Python 单元测试文档 涵盖了各个方面的综合指南 unittest框架。 它包括核心类、编写和运行测试的方法以及测试发现、测试加载器和测试套件等高级主题的详细描述。

它还提供了几个实际示例,帮助您了解如何在各种场景中有效地使用这些工具。 该文档会定期更新,因此它是有关最新功能和改进的可靠信息来源 unittest框架。

利用Python unittest文档将使您加深对 Python 单元测试的理解。 您对这些工具和技术越熟悉,您就越能编写有效的测试,确保您的代码按预期工作。

总之,Python 单元测试是每个 Python 开发人员都应该努力掌握的强大实践。 它是编写健壮、可靠的软件的关键组件。 请记住,良好测试的关键不仅仅是测试,而且是有效的测试。 这需要了解您可用的工具和技术,遵循最佳实践,并不断学习和调整您的测试方法以满足项目的需求。

正文完
 
luge
版权声明:本站原创文章,由 luge 2023-11-29发表,共计14344字。
转载说明:
评论(没有评论)
验证码