• 【Python百宝箱】Python测试工具大揭秘:从单元测试到Web自动化


    前言

    在现代软件开发中,测试是确保代码质量和稳定性的关键步骤。Python作为一门广泛应用的编程语言,拥有丰富的测试工具和库,从单元测试到Web自动化,覆盖了多个测试层面。本文将介绍一系列Python测试工具,帮助开发者选择适合项目需求的工具,提高代码的可靠性和可维护性。

    欢迎订阅专栏:Python库百宝箱:解锁编程的神奇世界

    文章目录

    Python测试大揭秘:从单元到Web,打造稳固代码的利器

    1. unittest / pytest

    1.1 单元测试框架

    单元测试是一种测试方法,旨在验证程序的最小可测试单元(通常是函数或方法)的正确性。Python内置了unittest模块,提供了一个传统的面向对象的单元测试框架。此框架基于测试类和测试方法的概念,通过断言来验证代码的预期行为。

    1.1.1 unittest示例

    下面是一个使用unittest进行单元测试的示例:

    import unittest
    
    def add(x, y):
        return x + y
    
    class TestAddFunction(unittest.TestCase):
        def test_add_positive_numbers(self):
            result = add(2, 3)
            self.assertEqual(result, 5)
    
        def test_add_negative_numbers(self):
            result = add(-2, 3)
            self.assertEqual(result, 1)
    
    if __name__ == '__main__':
        unittest.main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这个示例中,我们定义了一个简单的add函数,然后创建了一个继承自unittest.TestCase的测试类。测试类中包含了两个测试方法,分别测试了两种不同输入下的add函数的行为。unittest提供了多种断言方法,例如assertEqualassertTrue等,用于验证实际结果和期望结果是否一致。

    1.1.2 pytest示例

    pytest是另一个流行的测试框架,相比unittest更加简洁和灵活。使用pytest可以轻松编写并运行更具可读性的测试代码。

    def add(x, y):
        return x + y
    
    def test_add_positive_numbers():
        result = add(2, 3)
        assert result == 5
    
    def test_add_negative_numbers():
        result = add(-2, 3)
        assert result == 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    上述代码展示了相同的测试,但使用了pytest的语法。pytest使用assert语句进行断言,不需要继承特定的测试类。运行测试时,只需执行pytest命令即可自动发现并执行测试文件。

    1.2 参数化测试

    参数化测试是一种在单元测试中使用不同输入参数运行相同测试逻辑的方法。这在验证函数对多组输入的处理是否正确时非常有用。pytest通过@pytest.mark.parametrize装饰器提供了简洁的参数化测试支持。

    import pytest
    
    def add(x, y):
        return x + y
    
    @pytest.mark.parametrize("input_a, input_b, expected_result", [(2, 3, 5), (-2, 3, 1)])
    def test_addition(input_a, input_b, expected_result):
        result = add(input_a, input_b)
        assert result == expected_result
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在上述示例中,使用@pytest.mark.parametrize装饰器定义了多组输入参数和期望结果。pytest将自动为每组参数运行测试,并确保测试的全面性。

    1.3 测试固件

    测试固件是在执行测试前后进行一些准备和清理工作的机制。unittestpytest都提供了测试固件的支持。

    1.3.1 unittestsetUptearDown

    unittest中的测试固件可以通过setUptearDown方法实现,在每个测试方法执行前后执行相应的操作。

    import unittest
    
    class TestAddFunction(unittest.TestCase):
        def setUp(self):
            # 在每个测试方法执行前的准备工作
            print("Setting up test...")
    
        def tearDown(self):
            # 在每个测试方法执行后的清理工作
            print("Tearing down test...")
    
        def test_add_positive_numbers(self):
            result = add(2, 3)
            self.assertEqual(result, 5)
    
        def test_add_negative_numbers(self):
            result = add(-2, 3)
            self.assertEqual(result, 1)
    
    if __name__ == '__main__':
        unittest.main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    1.3.2 pytest的Fixture

    pytest中,可以使用@pytest.fixture装饰器创建测试固件。固件可以灵活地用于在测试之前进行设置和之后进行清理。

    import pytest
    
    @pytest.fixture
    def setup_teardown_example():
        # 在测试之前的准备工作
        print("Setting up test...")
    
        # 返回固件的值
        yield "example_value"
    
        # 在测试之后的清理工作
        print("Tearing down test...")
    
    def test_example(setup_teardown_example):
        value = setup_teardown_example
        assert value == "example_value"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    上述示例中,setup_teardown_example是一个固件函数,通过yield返回固件的值,并在yield之前和之后分别执行准备和清理工作。

    1.4 测试覆盖率

    测试覆盖率是一个衡量代码被测试程度的指标。coverage是一个用于测量测试覆盖率的工具。

    1.4.1 安装coverage

    使用以下命令安装coverage

    pip install coverage
    
    • 1
    1.4.2 使用coverage检测测试覆盖率

    在项目根目录下运行以下命令:

    coverage run -m pytest
    
    • 1

    运行测试后,生成测试覆盖率报告:

    coverage report -m
    
    • 1

    报告将显示每个文件的测试覆盖率百分比和详细的覆盖信息。

    2. nose2

    2.1 nose2测试框架

    nose2是一个灵活的测试框架,支持unittest测试用例,同时提供了许多插件来扩展功能。相较于unittestnose2更加灵活,允许开发者更自由地组织和运行测试。

    2.1.1 nose2的安装

    使用以下命令安装nose2

    pip install nose2
    
    • 1
    2.1.2 nose2示例

    下面是一个简单的使用nose2的示例:

    def multiply(x, y):
        return x * y
    
    def test_multiply_positive_numbers():
        result = multiply(2, 3)
        assert result == 6
    
    def test_multiply_negative_numbers():
        result = multiply(-2, 3)
        assert result == -6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用nose2运行测试:

    nose2
    
    • 1

    nose2会自动发现并执行测试。通过插件系统,你可以根据需要添加更多功能,例如测试覆盖率、测试报告等。

    2.1.3 参数化测试

    nose2同样支持参数化测试。使用@nose2.tools.params装饰器可以很容易地定义多组参数。

    from nose2.tools import params
    
    def add(x, y):
        return x + y
    
    @params((2, 3, 5), (-2, 3, 1))
    def test_addition(input_a, input_b, expected_result):
        result = add(input_a, input_b)
        assert result == expected_result
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在上述示例中,@params装饰器定义了多组输入参数和期望结果。nose2会为每组参数执行测试,使得测试代码更具可读性和复用性。

    2.1.4 插件扩展

    nose2的插件系统允许你根据项目需求自定义测试框架的行为。例如,可以使用nose2--with-coverage选项来集成测试覆盖率:

    nose2 --with-coverage
    
    • 1

    此命令将显示测试覆盖率报告,帮助你了解代码的测试程度。

    3. coverage

    3.1 代码覆盖率工具

    coverage是用于度量代码覆盖率的工具,帮助识别哪些部分的代码没有被测试覆盖。

    3.1.1 安装coverage

    使用以下命令安装coverage

    pip install coverage
    
    • 1
    3.1.2 使用coverage检测测试覆盖率

    在项目根目录下运行以下命令,使用coverage运行测试:

    coverage run -m pytest
    
    • 1

    运行测试后,生成测试覆盖率报告:

    coverage report -m
    
    • 1
    3.1.3 生成HTML格式的覆盖率报告

    coverage还支持生成HTML格式的覆盖率报告,提供更直观的可视化结果。运行以下命令生成HTML报告:

    coverage html
    
    • 1

    打开生成的htmlcov/index.html文件即可查看详细的代码覆盖率情况。

    3.1.4 忽略特定文件或目录

    有时候,你可能希望忽略一些文件或目录,不计入测试覆盖率。可以通过在项目根目录下创建.coveragerc文件,配置需要忽略的文件或目录:

    [run]
    omit =
        */virtualenvs/*
        */tests/*
    
    • 1
    • 2
    • 3
    • 4

    上述配置表示忽略所有位于virtualenvstests目录下的文件。

    3.1.5 使用coveragepytest结合

    coverage可以与pytest结合使用,提供更全面的测试覆盖率信息。在运行pytest时,可以使用--cov选项指定要进行覆盖率分析的模块或目录:

    pytest --cov=my_module
    
    • 1

    这将运行测试并生成关于my_module模块的测试覆盖率报告。

    4. mock

    4.1 模拟测试对象的库

    在单元测试中,有时需要模拟对象以隔离被测代码。mock库提供了创建虚拟对象的方法。

    4.1.1 安装mock

    如果你使用的是 Python 3.3 及更早版本,你需要额外安装 mock 库:

    pip install mock
    
    • 1

    从 Python 3.4 版本开始,mock库被添加到标准库中,因此在较新的 Python 版本中,你无需安装。

    4.1.2 mock示例代码

    下面是一个使用mock库的简单示例:

    from unittest.mock import Mock
    
    # 创建一个虚拟对象
    fake_object = Mock()
    
    # 设置虚拟对象的行为
    fake_object.method.return_value = 42
    
    # 在被测代码中使用虚拟对象
    result = fake_object.method()
    print(result)  # 输出: 42
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这个示例中,我们使用 Mock 类创建了一个虚拟对象 fake_object。然后,通过 fake_object.method.return_value,我们设置了虚拟对象的方法 method 的返回值为 42。最后,在被测代码中,我们调用了虚拟对象的方法,并打印了结果。

    4.1.3 模拟属性和方法

    mock库允许模拟对象的属性和方法。以下是一个模拟对象属性和方法的示例:

    from unittest.mock import Mock
    
    # 创建一个虚拟对象
    fake_object = Mock()
    
    # 模拟对象的属性
    fake_object.attribute = 'mocked_attribute'
    print(fake_object.attribute)  # 输出: mocked_attribute
    
    # 模拟对象的方法
    fake_object.method.return_value = 'mocked_method'
    result = fake_object.method()
    print(result)  # 输出: mocked_method
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这个示例中,我们模拟了对象的属性 attribute 和方法 method

    4.1.4 侧重点:隔离测试

    mock库的关键优势之一是它可以帮助隔离测试。通过模拟对象的行为,你可以确保被测代码仅依赖于被直接测试的功能,而不受外部因素的影响。

    4.1.5 Mocking 外部依赖

    在实际单元测试中,mock库常用于模拟外部依赖,例如数据库访问、API调用等。这有助于确保测试的独立性,即使外部服务不可用也能进行测试。

    from unittest.mock import Mock, patch
    
    def fetch_data_from_api():
        # 实际的 API 调用
        # ...
    
    def my_function_using_api():
        data = fetch_data_from_api()
        # 处理从 API 获取的数据
        return data
    
    # 使用 patch 来模拟 API 调用
    with patch('__main__.fetch_data_from_api') as mock_fetch:
        mock_fetch.return_value = {'key': 'value'}
        result = my_function_using_api()
    
    print(result)  # 输出: {'key': 'value'}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这个示例中,patch函数用于模拟 fetch_data_from_api 函数,确保测试的独立性。这种方法允许在测试过程中忽略外部依赖的具体实现。

    5. Faker

    5.1 生成测试数据的库

    在测试过程中,生成真实且多样的测试数据是很重要的。Faker库能够生成各种类型的假数据。

    5.1.1 安装Faker

    使用以下命令安装Faker

    pip install Faker
    
    • 1
    5.1.2 Faker示例代码

    下面是一个使用Faker库的简单示例:

    from faker import Faker
    
    fake = Faker()
    
    # 生成随机姓名
    name = fake.name()
    print(name)
    
    # 生成随机地址
    address = fake.address()
    print(address)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这个示例中,我们首先创建了一个 Faker 实例 fake。然后,通过调用 fake.name()fake.address() 方法,我们生成了随机的姓名和地址。

    5.1.3 Faker支持的数据类型

    Faker支持生成多种类型的假数据,包括但不限于:

    • 随机姓名:fake.name()
    • 随机地址:fake.address()
    • 随机文本:fake.text()
    • 随机邮箱:fake.email()
    • 随机日期:fake.date_of_birth()
    5.1.4 本地化数据生成

    Faker还支持本地化数据生成,以确保生成的假数据符合特定地区的语言和习惯。

    from faker import Faker
    
    # 创建一个带有特定语言和区域设置的 Faker 实例
    fake = Faker('zh_CN')
    
    # 生成随机姓名(中文)
    name = fake.name()
    print(name)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这个示例中,我们创建了一个使用中文语言和区域设置的 Faker 实例,然后生成了中文的随机姓名。

    5.1.5 数据生成策略定制

    Faker允许你定制数据生成策略,以满足特定的需求。你可以通过继承 Faker.Provider 类来创建自定义的数据生成器。

    from faker import Faker
    from faker.providers import BaseProvider
    
    class MyProvider(BaseProvider):
        def custom_data(self):
            # 自定义数据生成逻辑
            return "Custom Data"
    
    # 添加自定义数据生成器
    fake = Faker()
    fake.add_provider(MyProvider)
    
    # 使用自定义数据生成器
    custom_data = fake.custom_data()
    print(custom_data)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这个示例中,我们创建了一个自定义数据生成器 MyProvider,并通过 fake.add_provider(MyProvider) 将其添加到 Faker 实例中。然后,我们通过 fake.custom_data() 使用了自定义数据生成器。

    5.1.6 在测试中的应用

    在测试中使用 Faker 可以帮助生成丰富、真实的测试数据,提高测试的覆盖范围。例如,在测试数据库操作时,可以使用 Faker 生成随机的姓名、地址等字段值,以确保数据库操作的正确性和鲁棒性。

    6. Hypothesis

    6.1 基于属性的测试库

    Hypothesis是一个基于属性的测试库,它能够自动生成测试用例来检查代码的行为。

    6.1.1 安装Hypothesis

    使用以下命令安装Hypothesis

    pip install hypothesis
    
    • 1
    6.1.2 Hypothesis示例代码

    下面是一个使用Hypothesis的简单示例:

    from hypothesis import given
    from hypothesis.strategies import integers
    
    @given(integers(), integers())
    def test_addition_commutes(x, y):
        assert x + y == y + x
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这个示例中,我们使用 @given 装饰器来定义测试函数 test_addition_commutes,并使用 integers() 策略来生成两个整数 xyHypothesis会自动运行该测试,并生成大量的测试数据,以确保满足测试用例中定义的属性。

    6.1.3 策略和数据生成

    Hypothesis使用策略(strategies)来定义测试数据的生成规则。除了整数,还可以使用各种内置策略或组合策略来生成不同类型的数据。

    from hypothesis import given
    from hypothesis.strategies import text, lists
    
    @given(text(), lists(integers()))
    def test_example(text_data, list_data):
        # 测试逻辑
        ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这个示例中,我们使用 text() 策略生成字符串类型的数据,使用 lists(integers()) 策略生成整数列表类型的数据。

    6.1.4 复杂属性和假设

    Hypothesis支持定义复杂的属性和假设,以进一步精确地指导测试数据的生成。通过 assume 函数,你可以添加自定义的假设条件。

    from hypothesis import given, assume
    from hypothesis.strategies import integers
    
    @given(integers())
    def test_positive_numbers(x):
        assume(x > 0)
        assert x > 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这个示例中,我们使用 assume 函数确保生成的整数 x 大于 0。

    6.1.5 在实际项目中的应用

    Hypothesis在实际项目中的应用非常广泛,特别是对于需要大量测试用例、测试边界条件的项目。通过定义简洁的属性和策略,Hypothesis能够自动发现潜在的问题,并帮助你编写更健壮、全面的测试。

    6.1.6 与其他测试框架结合使用

    Hypothesis可以与其他测试框架(如pytestunittest)结合使用,以提供更强大的测试支持。例如,结合 pytest 使用:

    from hypothesis import given
    from hypothesis.strategies import integers
    import pytest
    
    @given(integers())
    def test_positive_numbers(x):
        assert x > 0
    
    if __name__ == '__main__':
        pytest.main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    7. Selenium

    7.1 自动化浏览器操作的库

    Selenium是一个用于自动化浏览器操作的库,广泛用于进行Web界面测试。

    7.1.1 安装Selenium

    使用以下命令安装Selenium

    pip install selenium
    
    • 1

    同时,你需要下载相应浏览器的驱动程序(如ChromeDriver)并确保它在系统的PATH中。

    7.1.2 Selenium示例代码

    下面是一个使用Selenium的简单示例:

    from selenium import webdriver
    
    # 创建浏览器对象
    driver = webdriver.Chrome()
    
    # 打开网页
    driver.get("https://www.example.com")
    
    # 执行一些操作,如点击按钮、填写表单等
    
    # 关闭浏览器
    driver.quit()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这个示例中,我们首先创建了一个 webdriver.Chrome() 实例,表示使用Chrome浏览器。然后,使用 driver.get("https://www.example.com") 打开了一个网页。在实际测试中,你可以执行各种操作,如点击按钮、填写表单等。

    7.1.3 元素定位和交互

    Selenium提供了丰富的方法来定位页面元素,并与这些元素进行交互。例如,通过find_element_by_xpath方法定位元素:

    # 定位元素并点击
    button = driver.find_element_by_xpath("//button[@id='submit-button']")
    button.click()
    
    # 定位文本框并输入文本
    input_box = driver.find_element_by_name("username")
    input_box.send_keys("example_user")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    7.1.4 等待机制

    在Web界面测试中,页面元素可能需要时间加载,Selenium提供了等待机制来应对这种情况。例如,使用WebDriverWait等待某个元素出现:

    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    
    # 等待元素出现
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "example-element"))
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    7.1.5 测试框架整合

    Selenium可以与各种测试框架(如unittestpytest)整合使用,以便更好地管理测试用例。以下是一个使用pytest的简单示例:

    import pytest
    from selenium import webdriver
    
    @pytest.fixture
    def browser():
        driver = webdriver.Chrome()
        yield driver
        driver.quit()
    
    def test_example(browser):
        browser.get("https://www.example.com")
        assert "Example Domain" in browser.title
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    7.1.6 高级功能和浏览器兼容性

    Selenium支持许多高级功能,如浏览器的无头模式、浏览器的多窗口管理等。同时,你可以选择使用不同的浏览器驱动程序(如Firefox、Edge等)来适应不同的需求。

    8. requests-mock

    8.1 模拟HTTP请求响应的库

    在进行API测试时,经常需要模拟HTTP请求以确保代码在不同网络条件下的行为。requests-mock是一个方便的工具。

    8.1.1 安装requests-mock

    使用以下命令安装requests-mock

    pip install requests-mock
    
    • 1
    8.1.2 requests-mock示例代码

    下面是一个使用requests-mock的简单示例:

    import requests
    from requests_mock import Mocker
    
    with Mocker() as mocker:
        # 设置模拟的响应
        mocker.get('https://api.example.com/data', text='Mocked Response')
    
        # 发起HTTP请求
        response = requests.get('https://api.example.com/data')
    
        # 断言请求的响应
        assert response.text == 'Mocked Response'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这个示例中,我们使用 requests-mockMocker 上下文管理器来模拟HTTP请求和响应。通过 mocker.get 方法设置了对特定URL的GET请求的模拟响应。然后,我们使用 requests.get 发起了实际的HTTP请求,并通过断言确保了得到了模拟的响应。

    8.1.3 模拟不同的HTTP方法和状态码

    requests-mock支持模拟不同的HTTP方法(GET、POST、PUT等)和状态码。以下是一个示例:

    import requests
    from requests_mock import Mocker
    
    with Mocker() as mocker:
        # 模拟POST请求和自定义状态码
        mocker.post('https://api.example.com/data', text='Created', status_code=201)
    
        # 发起POST请求
        response = requests.post('https://api.example.com/data')
    
        # 断言请求的响应和状态码
        assert response.text == 'Created'
        assert response.status_code == 201
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    8.1.4 模拟异常和超时

    在测试中,有时需要模拟HTTP请求的异常和超时情况。requests-mock可以轻松应对这些情况:

    import requests
    from requests_mock import Mocker
    
    with Mocker() as mocker:
        # 模拟请求超时
        mocker.get('https://api.example.com/timeout', exc=requests.exceptions.Timeout)
    
        try:
            # 发起GET请求
            response = requests.get('https://api.example.com/timeout')
        except requests.exceptions.Timeout as e:
            # 处理超时异常
            print(f"Request timed out: {e}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    8.1.5 在测试中的应用

    在进行API测试时,使用 requests-mock 可以方便地模拟不同的场景,包括正常响应、异常、超时等,以确保代码在各种情况下的稳健性。

    总结

    通过本文的介绍,读者将对Python中丰富的测试工具有更全面的了解。从编写单元测试到模拟测试对象,再到生成测试数据和进行Web界面测试,每个工具都有其独特的优势。选择适合项目需求的测试工具,将有助于提高代码质量、减少错误和提升开发效率。希望本文对读者在Python开发中的测试实践提供有益的指导和启示。

  • 相关阅读:
    启动1000万个虚拟线程需要多少时间?需要多少平台线程?
    docker基本使用
    matlab新建数据字典及如何导入
    报错处理:Nginx端口被占用
    指针练习(2)
    Vue3简单使用(一) --- 环境搭建
    Analyzing and Leveraging Decoupled L1 Caches in GPUs
    Canal增量订阅MySQL数据同步工具
    Jenkins 的构建时执行时间问题
    JAVA架构之路(二)
  • 原文地址:https://blog.csdn.net/qq_42531954/article/details/134542054