【代码共享】python测试经常用到的第三方库详细解说

在Python测试领域,常用的第三方库非常多,以下是一些关键的测试相关库:

  1. unittest:Python自带的标准单元测试框架。

  2. pytest

    • 是一个非常流行且功能强大的测试框架,支持简单的断言、参数化测试、插件机制等。可以与unittest兼容,但提供了更丰富的功能和更简洁的语法。
  3. nose:曾经很受欢迎的测试工具,它扩展了unittest的功能,使得测试编写更加简单高效,但随着pytest的发展,其使用已逐渐减少。

  4. unittest.mock

    • Python标准库的一部分,用于创建模拟对象(mock objects)以方便进行模块间的隔离测试。
  5. mock

    • 在Python 3.3之前是一个独立的库(后来被合并入标准库),提供了一种创建和配置mock对象的方法,便于测试时模拟复杂的依赖关系。
  6. hypothesis

    • 用于生成假设性输入数据的库,可帮助自动化查找边界条件和其他潜在问题。
  7. tox

    • 自动化测试环境管理和多版本测试工具,可以在不同Python版本及环境下运行测试。
  8. coverage

    • 测试覆盖率分析工具,可以报告代码中哪些部分经过了测试,哪些还没有。
  9. seleniumpytest-selenium

    • Selenium是Web应用程序自动化测试工具,而pytest-selenium则是将Selenium集成到pytest中的插件,用于进行浏览器端的自动化UI测试。
  10. requests-mock

    • 用于HTTP请求相关的接口测试,能够模拟网络响应。
  11. factory_boyFaker

    • 这两个库都可以用来生成测试用例所需的假数据。
  12. behave

    • BDD(行为驱动开发)风格的测试框架,适合于编写用户故事级别的测试用例。
  13. sqlalchemy-utilsdataset

    • 提供数据库相关的测试辅助工具,如临时数据库管理、填充测试数据等。

以上仅列举了部分常用库,实际根据项目需求还可能用到更多其他类型的测试工具,例如性能测试工具(locust, ab)、安全测试工具(bandit, zaproxy)、持续集成和部署工具(Jenkins, Travis CI)等。

unittest:Python自带的标准单元测试框架。举例

以下是一个使用Python自带的unittest单元测试框架编写的简单测试用例示例。假设我们有一个简单的数学函数add(),需要编写一个测试类来验证其正确性。

python
# 首先定义被测试的模块或函数
def add(a, b):
    return a + b

# 然后创建一个测试类,该类继承自unittest.TestCase
import unittest

class TestAddFunction(unittest.TestCase):

    # 定义测试方法,通常以test开头
    def test_addition(self):
        # 断言预期结果与实际结果相等
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(-1, 5), 4)
        self.assertEqual(add(0, 0), 0)

    # 可以添加更多的测试方法来测试其他场景
    def test_edge_cases(self):
        self.assertEqual(add(float('inf'), float('-inf')), float('nan'))
        self.assertEqual(add(1.5, 2.7), 4.2)  # 小数加法测试
        self.assertEqual(add('', ''), '')  # 如果add函数支持字符串连接操作,也可进行相关测试

if __name__ == '__main__':
    # 运行测试套件
    unittest.main()

在上述代码中:

  • TestAddFunction 类继承了 unittest.TestCase 类,从而获得了所有用于执行断言的方法。
  • 每个以 test_ 开头的方法代表一个独立的测试用例。
  • 使用 assertEqual() 方法来进行断言,检查实际结果是否等于预期结果。

运行此脚本时,unittest会自动找到所有的test_*方法并依次执行它们,如果所有断言都通过,则表明测试成功;否则,如果有任何断言失败,unittest将打印出详细的错误信息。

pytest:举例

pytest 是一个非常灵活且强大的Python测试框架,下面是一个使用pytest编写的简单单元测试示例:

假设我们有一个简单的数学函数multiply()需要进行测试:

python
# 被测试的模块或函数
def multiply(x, y):
    return x * y

# 在另一个文件(如:test_multiply.py)中编写测试用例
import pytest

# 使用@pytest.mark.parametrize可以参数化测试用例
@pytest.mark.parametrize("x, y, expected", [
    (1, 2, 2),
    (3, 4, 12),
    (-1, 5, -5),
    (0, 0, 0)
])
def test_multiply(x, y, expected):
    assert multiply(x, y) == expected

# 另一个独立的测试方法
def test_multiply_with_strings():
    with pytest.raises(TypeError):  # 断言函数在接收到非数字类型时会抛出TypeError异常
        multiply('a', 'b')

if __name__ == '__main__':
    # 不需要像unittest那样显式调用运行,直接运行此脚本即可执行所有pytest标记的测试用例
    pytest.main()

在这个例子中:

  • 使用 pytest.mark.parametrize 装饰器来定义一组参数化的测试用例,这样可以一次性为多种输入和预期输出执行同一个测试函数。
  • 测试方法名不需要以 test_ 开头,但通常遵循这一约定以方便识别。
  • 断言通过 assert 语句实现,比如这里检查 multiply() 函数返回的结果是否等于预期值。
  • 使用 with pytest.raises(TypeError) 来断言当函数接收到错误类型的参数时应该抛出特定类型的异常。

执行测试只需在命令行中运行 pytest test_multiply.py 即可。pytest会自动发现并运行所有名为“test_*”的函数作为测试用例,并提供详细的测试报告。

nose使用举例

由于nose库已经不再维护,这里提供一个基于旧版本的nose框架编写的单元测试示例。假设我们有一个简单的数学函数subtract()需要进行测试:

python
# 被测试的模块或函数
def subtract(a, b):
    return a - b

# 在另一个文件(如:test_subtract.py)中编写测试用例
import unittest
from nose.tools import assert_equal, assert_raises

class TestSubtractFunction(unittest.TestCase):

    def test_subtraction(self):
        assert_equal(subtract(5, 2), 3)
        assert_equal(subtract(-3, 2), -5)
        assert_equal(subtract(0, 0), 0)

    def test_edge_cases(self):
        with assert_raises(TypeError):  # 断言在接收到非数字类型时会抛出TypeError异常
            subtract('a', 'b')

if __name__ == '__main__':
    import nose
    nose.runmodule()  # 运行所有nose发现的测试用例

在这个例子中:

  • 我们创建了一个继承自unittest.TestCase的类,并在其内部定义了测试方法。
  • 使用nose.tools提供的assert_equal辅助函数来验证结果是否等于预期值。
  • 使用assert_raises上下文管理器来断言当函数接收到错误类型的参数时应该抛出特定类型的异常。

运行这个测试脚本可以通过命令行执行 nosetests test_subtract.py(对于旧版nose)。nose框架会自动发现并运行所有的测试用例。但请注意,由于nose的维护状态和Python生态系统的发展,推荐使用pytest等更为活跃且功能更丰富的测试框架进行现代项目的测试。

unittest.mock举例

在Python的unittest.mock库中,我们可以创建和使用mock对象来模拟复杂的依赖关系,以便在单元测试中隔离被测代码。以下是一个使用unittest.mock进行单元测试的简单示例:

假设有一个模块my_module,其中有一个函数fetch_data()会调用外部API获取数据,并且我们想要测试一个依赖于fetch_data()结果的函数process_data(),但不想在测试过程中实际调用外部API。

python
# 假设这是要测试的模块(my_module.py)
import requests

def fetch_data(url):
    response = requests.get(url)
    return response.json()

def process_data(url):
    data = fetch_data(url)
    # 对data进行处理...
    processed_data = ...
    return processed_data

# 测试文件(test_my_module.py)
import unittest
from unittest.mock import patch, Mock
from my_module import process_data

class TestProcessData(unittest.TestCase):

    @patch('my_module.fetch_data')
    def test_process_data(self, mock_fetch_data):
        # 创建模拟的响应数据
        mock_response = {'key': 'value'}
        mock_fetch_data.return_value = mock_response  # 设置mock方法的返回值
        
        url = 'https://example.com/api/data'
        result = process_data(url)

        # 验证过程
        mock_fetch_data.assert_called_once_with(url)  # 确保fetch_data()被正确调用
        self.assertEqual(result, ...)  # 根据实际情况验证processed_data的结果

if __name__ == '__main__':
    unittest.main()

在这个例子中:

  1. 我们使用@patch('my_module.fetch_data')装饰器来替换my_module模块中的fetch_data()函数为一个Mock对象。
  2. 在测试方法内部,通过mock_fetch_data.return_value设置当调用mock对象时应该返回的值。
  3. 执行process_data()函数,由于fetch_data()已经被替换为mock对象,因此不会真正执行网络请求。
  4. 使用mock_fetch_data.assert_called_once_with(url)验证fetch_data()是否被正确地以预期参数调用了一次。
  5. 最后,根据实际情况对process_data()的返回结果进行断言验证。

mock:举例

mock库(在Python 3.3及更高版本中已并入unittest.mock)用于创建和配置模拟对象,以便在测试时替换掉那些难以或不应直接调用的真实对象。以下是一个使用mock库的简单示例:

假设有一个模块my_module,其中有一个函数send_email()会调用SMTP服务发送电子邮件,但在单元测试中我们并不希望实际发送邮件。

python
# 假设这是要测试的模块(my_module.py)
import smtplib

def send_email(subject, message, to):
    server = smtplib.SMTP('smtp.example.com')
    server.sendmail('from@example.com', to, f'Subject: {subject}\n\n{message}')
    server.quit()

# 测试文件(test_my_module.py)
from unittest.mock import MagicMock
from my_module import send_email

class TestSendEmail(unittest.TestCase):

    def test_send_email(self):
        # 创建一个模拟的SMTP服务器对象
        mock_server = MagicMock()
        
        # 将smtplib.SMTP()返回值设置为模拟服务器对象
        with patch('smtplib.SMTP') as MockSMTP:
            MockSMTP.return_value = mock_server
            
            subject = 'Test Email'
            message = 'This is a test email.'
            to = 'recipient@example.com'
            
            send_email(subject, message, to)
            
            # 验证过程
            MockSMTP.assert_called_once_with('smtp.example.com')
            mock_server.sendmail.assert_called_once_with(
                'from@example.com',
                to,
                f'Subject: {subject}\n\n{message}'
            )
            mock_server.quit.assert_called_once()

if __name__ == '__main__':
    unittest.main()

在这个例子中:

  1. 使用patch('smtplib.SMTP')装饰器来替换smtplib.SMTP类为一个Mock对象。
  2. 设置MockSMTP.return_value为一个模拟的SMTP服务器对象(mock_server),这样当我们调用smtplib.SMTP()时,将返回这个模拟对象而不是真实的SMTP服务器实例。
  3. 调用send_email()函数,由于SMTP服务器已经被替换为模拟对象,因此不会真的发送邮件。
  4. 使用MockSMTP.assert_called_once_with('smtp.example.com')验证是否正确地尝试连接到指定的SMTP服务器地址。
  5. 使用mock_server.sendmail.assert_called_once_with(...)mock_server.quit.assert_called_once()来验证邮件发送和关闭连接方法是否按预期被调用。

hypothesis:举例

Hypothesis 是一个用于Python的假设性测试库,它能够生成和缩小假设性的输入数据来帮助我们找到代码中的bug。以下是一个使用Hypothesis进行假设性测试的简单示例:

假设我们有一个函数is_palindrome(),用于检查一个字符串是否为回文(正读反读都一样)。

python
def is_palindrome(s):
    # 简化的实现,仅支持英文字符且忽略空格、标点符号等
    s = ''.join(e for e in s if e.isalnum()).lower()
    return s == s[::-1]

# 测试文件(test_is_palindrome.py)
from hypothesis import given, strategies as st
import unittest

class TestIsPalindrome(unittest.TestCase):

    @given(st.text())
    def test_is_palindrome(self, text):
        """
        假设性测试:对于任何由Hypothesis生成的文本输入,
        如果该文本是回文,则is_palindrome函数应返回True。
        """
        cleaned_text = ''.join(e for e in text if e.isalnum()).lower()
        actual_result = is_palindrome(text)
        expected_result = cleaned_text == cleaned_text[::-1]
        
        self.assertEqual(actual_result, expected_result)

if __name__ == '__main__':
    unittest.main()

在这个例子中:

  1. 使用 @given(st.text()) 装饰器指定测试函数将接收Hypothesis自动生成的各种文本输入。
  2. 在测试方法内部,我们计算了传入字符串清理后的回文状态,并与is_palindrome()函数的结果进行比较。
  3. Hypothesis会尝试生成大量的输入数据以确保我们的函数在各种可能的情况下都能正确工作。

请注意,由于Hypothesis的强大功能,它可能会发现我们简化版is_palindrome函数的问题,因为它没有处理非英文字符。在实际项目中,你可能需要为text策略添加更多的约束条件以匹配你的函数需求。

tox:举例

Tox是一个用于Python项目的虚拟环境管理和测试工具。它可以自动化地为项目创建多个独立的虚拟环境,并在每个环境中安装依赖并运行测试。下面是一个使用tox进行多环境测试的基本配置和使用示例:

  1. 配置 tox.ini 文件: 在项目根目录下创建一个名为 tox.ini 的文件,其中包含以下内容:
ini
[tox]
envlist = py36, py37, py38, py39  # 测试Python 3.6至3.9版本

[testenv]
deps =
    pytest
commands =
    pytest

在这个例子中,我们定义了要测试的Python环境列表(py36到py39),并在每个环境中安装pytest作为测试依赖,并执行pytest命令运行测试。

  1. 项目结构: 假设我们的项目结构如下:
code
project/
    src/
        mymodule.py
    tests/
        test_mymodule.py
    tox.ini
  1. tests/test_mymodule.py 中编写针对 mymodule.py 的测试用例。

  2. 运行 tox: 在命令行中,切换到项目根目录下,然后运行 tox 命令:

bash
cd /path/to/project
tox

这个命令将自动为定义的每一个Python环境创建一个虚拟环境,安装指定的依赖(pytest),然后在每个环境中运行pytest命令以执行所有测试用例。

通过这种方式,您可以确保代码在不同Python版本下都能正确运行。此外,您还可以在tox配置中添加更多的复杂设置,比如仅针对特定环境运行部分测试、调整环境变量等。

coverage:举例

coverage 是一个用于测量Python代码覆盖率的工具,它能告诉你测试运行时哪些代码行被执行了,哪些没有被执行。下面是一个使用coverage进行代码覆盖率测试的基本示例:

  1. 编写被测模块和测试用例: 假设我们有一个简单的 mymodule.py 模块,以及对应的测试文件 test_mymodule.py

python
# mymodule.py
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b
python
# test_mymodule.py
import unittest
from mymodule import add, subtract

class TestMyModule(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(1, 2), 3)

    def test_subtract(self):
        self.assertEqual(subtract(3, 2), 1)

if __name__ == '__main__':
    unittest.main()
  1. 配置并运行 coverage: 在命令行中,先安装coverage库(如果尚未安装):pip install coverage

然后执行以下命令以生成覆盖率报告:

bash
coverage run --source=./mymodule -m unittest discover
coverage report -m
  • coverage run 命令会运行你的测试,并记录哪些代码行被执行。
  • --source=./mymodule 参数指定了要分析的源码目录或模块。
  • -m unittest discover 自动发现并运行当前目录下的所有unittest测试用例。
  • coverage report -m 生成并显示覆盖率报告,包括每个模块的百分比以及详细行数统计。

执行上述命令后,你会看到类似这样的输出:

这表示在测试过程中,mymodule.pytest_mymodule.py 的所有代码行都被执行到了,覆盖率是100%。

selenium 和 pytest-selenium:举例

Selenium 是一个用于自动化浏览器操作的工具,它可以模拟用户对网页的各种交互行为,如点击、填写表单等。而 pytest-selenium 是将 Selenium 集成到 pytest 测试框架的一个插件,可以方便地编写和组织 UI 自动化测试用例。

下面是一个使用 pytest 和 pytest-selenium 编写的简单 Web 测试示例:

首先,确保安装了相关库:

bash
pip install pytest selenium pytest-selenium

然后创建一个测试用例文件(例如:test_web_ui.py):

python
# 导入所需模块
import pytest
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

@pytest.fixture
def browser():
    # 初始化 Chrome 浏览器驱动
    driver = webdriver.Chrome()
    yield driver  # 在测试函数执行期间,yield返回的driver可供使用
    driver.quit()  # 测试结束后关闭浏览器

def test_google_search(browser):
    # 使用fixture注入的浏览器对象访问Google首页
    browser.get('http://www.google.com')
    
    # 找到搜索框元素并输入文本
    search_box = browser.find_element_by_name('q')
    search_box.send_keys('pytest+selenium example')
    search_box.send_keys(Keys.RETURN)  # 模拟回车键按下进行搜索
    
    # 确保搜索结果页面标题包含预期关键字
    assert 'pytest+selenium' in browser.title.lower()

# 运行测试
if __name__ == "__main__":
    pytest.main(["-v", "test_web_ui.py"])

在这个例子中:

  1. 我们定义了一个名为 browser 的 pytest fixture,它初始化一个 Chrome 浏览器实例,并在每个测试方法执行前后分别打开和关闭浏览器。
  2. test_google_search 方法是实际的测试用例,它使用 Selenium API 访问 Google 主页,填写搜索框并执行搜索,最后检查搜索结果页面的标题是否包含预期的关键词。

要运行这个测试,直接在命令行中执行 pytest test_web_ui.py 即可。如果一切配置正确,该测试会自动启动Chrome浏览器,完成指定的Web操作,并验证结果。

 requests-mock:举例

requests-mock 是一个用于模拟 HTTP 请求和响应的库,可以方便地在测试中替代实际的网络请求。以下是一个使用 requests-mock 进行单元测试的基本示例:

首先安装 requests 和 requests-mock 库(如果尚未安装):

bash
pip install requests requests-mock

假设我们有一个函数 fetch_data() 使用 requests 库从特定 API 获取数据:

python
# fetch_data.py
import requests

def fetch_data(url):
    response = requests.get(url)
    return response.json()

我们可以编写一个测试用例,使用 requests-mock 模拟对 API 的 GET 请求并返回预设的响应内容:

python
# test_fetch_data.py
import unittest
from unittest.mock import patch
import requests_mock
from fetch_data import fetch_data

class TestFetchData(unittest.TestCase):

    @patch('fetch_data.requests')
    def test_fetch_data(self, mock_requests):
        # 预设模拟响应
        mock_response = {
            'key': 'value'
        }
        expected_result = mock_response

        # 设置模拟API响应
        with requests_mock.Mocker() as m:
            m.get('http://example.com/api/data', json=mock_response)

            result = fetch_data('http://example.com/api/data')

        # 验证结果
        self.assertEqual(result, expected_result)

if __name__ == '__main__':
    unittest.main()

在这个例子中:

  1. 我们使用 @patch('fetch_data.requests') 装饰器将 requests 模块替换为一个 Mock 对象。
  2. 在测试方法内部,通过 requests_mock.Mocker() 创建一个模拟器,并设置当访问指定 URL 时应返回的 JSON 数据。
  3. 执行 fetch_data() 函数,由于使用了模拟器,实际并未发送网络请求。
  4. 最后验证函数返回的结果是否与预设的模拟响应相同。

运行测试用例:

bash
python test_fetch_data.py

factory_boy 或 Faker:举例

使用 factory_boy 创建假数据

首先安装 factory_boy 库(如果尚未安装):

bash
pip install faker

在测试中直接使用 Faker 来生成假数据:

python
from unittest import TestCase
from faker import Faker

class TestUserModel(TestCase):

    def setUp(self):
        self.fake = Faker()
        self.user_data = {
            'username': self.fake.user_name(),
            'email': self.fake.email(),
        }

    def test_user_data_format(self):
        self.assertIsNotNone(self.user_data['username'])
        self.assertIsNotNone(self.user_data['email'])
        self.assertTrue('@' in self.user_data['email'])  # 验证邮件地址包含@

    # 这里仅演示了生成和验证假数据,实际场景下可能需要将这些数据用于创建并测试模型实例
在这个例子中,Faker 自动生成了一个用户名和电子邮件地址,并在测试中验证了数据的有效性。你可以根据需求结合 factory_boy 和 Faker 来为模型实例提供更加丰富和真实的假数据。

behave:举例

behave 是一个用于 Python 的行为驱动开发(BDD)测试框架。它允许你以自然语言的形式描述用户故事,并为这些故事编写可执行的步骤定义。下面是一个使用 behave 编写用户故事和测试步骤的简单示例:

  1. 首先,安装 behave

bash
pip install behave

    2.创建项目结构:

code
project/
  features/
    - steps/
      - __init__.py
      - login_steps.py
    - login.feature
  tests/
    # 其他测试相关文件
  manage.py  # Django项目举例,非必须

编写 features/login.feature 文件(用户故事):

gherkin
Feature: User Login
  As a user,
  I want to be able to log into my account
  So that I can access secure information.

  Scenario: Successful login with valid credentials
    Given the user "Alice" exists with password "password"
    When the user "Alice" logs in with password "password"
    Then the login should be successful

  Scenario: Failed login with invalid password
    Given the user "Bob" exists with password "secure_password"
    When the user "Bob" logs in with password "wrong_password"
    Then the login should fail

编写步骤定义在 features/steps/login_steps.py 中:

python
from behave import given, when, then
from django.contrib.auth.models import User  # 假设我们使用Django

@given('the user "{username}" exists with password "{password}"')
def step_impl(context, username, password):
    User.objects.create_user(username=username, password=password)

@when('the user "{username}" logs in with password "{password}"')
def step_impl(context, username, password):
    context.response = context.client.post('/login/', data={'username': username, 'password': password})

@then('the login should be successful')
def step_impl(context):
    assert context.response.status_code == 200  # 根据实际应用情况调整状态码和检查条件

@then('the login should fail')
def step_impl(context):
    assert context.response.status_code != 200  # 或者根据错误消息或重定向等进行验证

运行 behave 测试:

bash
behave features/

通过这种方式,behave将读取login.feature中的用户故事,并执行相应的步骤定义来完成测试。每个步骤都对应一个Python函数,这些函数会在测试运行时被调用。

sqlalchemy-utils 或 dataset:举例

由于之前提到的dataset库主要是用于简化与SQLAlchemy进行数据库交互,而sqlalchemy-utils是一个提供额外工具和实用功能以增强SQLAlchemy体验的库。下面分别给出一个使用这两个库的基本示例:

sqlalchemy-utils 示例

首先安装 sqlalchemy-utils(如果尚未安装)

bash
pip install sqlalchemy-utils

假设我们已经设置了SQLAlchemy基础环境,并且有一个User模型:

python
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy_utils import ChoiceType

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    status = Column(ChoiceType(['active', 'inactive', 'pending'], impl=Integer))

在上述代码中,我们使用了sqlalchemy-utils提供的ChoiceType,它允许我们在数据库字段中存储枚举值并提供相应的类型检查。

dataset 示例

首先安装 dataset(如果尚未安装):

python
from dataset import Dataset

# 连接到SQLite数据库(或任何其他支持的数据库)
db = Dataset('sqlite:///example.db')

# 创建一个新的表
table = db['users']
table.create_column('id', type='int')
table.create_column('name', type='str')
table.create_column('status', type='str')  # 在这里可以设置为类似'active'、'inactive'的枚举值

# 插入数据
table.insert(dict(id=1, name='Alice', status='active'))
table.insert(dict(id=2, name='Bob', status='inactive'))

# 查询数据
for row in table:
    print(row)

# 更新数据
table.update(dict(name='Charlie'), ['id'], [1])

# 删除数据
table.delete('id = 2')

在这个例子中,dataset提供了简洁易用的API来操作数据库表,包括创建列、插入数据、查询、更新和删除记录。不过,对于复杂的数据类型如ChoiceTypedataset自身并不直接支持,通常需要结合SQLAlchemy原生的方式或者配合sqlalchemy-utils等库一起使用。

  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软件工匠手记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值