• 在这种情况下,你需要在解析 YAML 文件的时候,根据需要执行的函数名称来确定所需导入的模块。可以通过预先定义一个映射关系,将函数名称与对应的模块进行关联。

    以下是一个示例代码,演示了如何根据函数名动态导入模块并执行对应的函数:

    import re
    import importlib
    
    # 解析并执行函数
    def execute_function(module_name, function_name, *args):
        # 动态导入模块
        module = importlib.import_module(module_name)
        # 获取函数对象
        function = getattr(module, function_name)
        # 执行函数
        result = function(*args)
        return result
    
    # 原始用例内容
    test_case = """
    api: /user/register
    data:
      mobile: ${moduleA.get_phone()}
      name: John Doe
    """
    
    # 使用正则表达式匹配并提取函数调用
    regex_pattern = r'\$\{(.+?)\}'
    matches = re.findall(regex_pattern, test_case)
    
    # 遍历匹配结果,解析模块名和函数名,并执行对应的函数
    for match in matches:
        module_function = match.strip()
        module_name, function_name = module_function.split('.', 1)  # 分解模块名和函数名
        function_args = ()  # 如果需要传参,可以解析参数列表
        function_result = execute_function(module_name, function_name, *function_args)  # 执行函数
        test_case = test_case.replace(f"${{{module_function}}}", function_result)
    
    print(test_case)
    

    在这个示例中,我们使用 importlib.import_module() 方法根据模块名动态导入模块。然后,我们使用 getattr() 方法获取函数对象,并执行该函数。

    需要注意的是,这里使用的是动态导入模块的方式,你需要确保模块名是可靠的、可信任的,并且模块包含了需要调用的函数。另外,对于函数的参数也可以在解析时进行进一步处理,以满足你的需求。

  • 是的,你的理解是正确的。在解析用例 YAML 文件时,你可以将模块名和函数名都包含在参数中,例如 ${moduleA.get_phone()}

    在解析用例时,你可以通过正则表达式或其他方法提取出 ${moduleA.get_phone()},然后对其进行解析和分解得到模块名和函数名。

    以下是一个示例代码,演示了如何解析 ${moduleA.get_phone()} 并执行对应的函数:

    import re
    
    # 解析并执行函数
    def execute_function(module_name, function_name, *args):
        # 模拟执行函数的逻辑
        result = f"Executing {module_name}.{function_name} with arguments: {args}"
        return result
    
    # 原始用例内容
    test_case = """
    api: /user/register
    data:
      mobile: ${moduleA.get_phone()}
      name: John Doe
    """
    
    # 使用正则表达式匹配并提取函数调用
    regex_pattern = r'\$\{(.+?)\}'
    matches = re.findall(regex_pattern, test_case)
    
    # 遍历匹配结果,解析模块名和函数名,并执行对应的函数
    for match in matches:
        module_function = match.strip()
        module_name, function_name = module_function.split('.', 1)  # 分解模块名和函数名
        function_args = ()  # 如果需要传参,可以解析参数列表
        function_result = execute_function(module_name, function_name, *function_args)  # 执行函数
        test_case = test_case.replace(f"${{{module_function}}}", function_result)
    
    print(test_case)
    

    在这个示例中,我们将 ${moduleA.get_phone()} 作为函数调用的格式,解析出模块名和函数名。然后,我们调用 execute_function() 来执行对应的函数。

    需要注意的是,这里只是一个简单的示例,假设函数的模块名是固定的。如果模块名是动态的,你可能需要根据实际情况进行相应的调整。另外,对于函数参数也可以在解析时进行进一步处理,以满足你的需求。

  • 要解决这个问题,您可以尝试以下几个步骤:

    1. 确保您的 HTML 代码正确:检查 checkbox 元素的 id、name、value 和其他属性是否正确设置。确保没有设置 disabled 属性,否则它将禁用 checkbox。

    2. 检查 CSS 样式:查看是否有任何 CSS 样式或脚本在页面上覆盖了 checkbox 元素的默认行为。可能存在某些样式或脚本会导致 checkbox 无法被勾选。

    3. 检查其他 JavaScript 代码:确保没有其他 JavaScript 代码在页面上干扰 checkbox 的勾选状态。可能存在其他代码在点击事件中修改了 checkbox 的状态,导致它看起来无法被勾选。

    4. 使用 JavaScript 设置勾选状态:如果以上步骤都没有解决问题,您可以尝试使用 JavaScript 代码来设置 checkbox 的勾选状态。您可以使用 document.getElementById("checkboxId").checked = true 来将 checkbox 的 checked 属性设置为 true,从而强制勾选它。

    如果尝试了以上步骤仍然无法解决问题,那可能是因为浏览器插件的特殊性导致的,您可能需要查阅相关的浏览器插件文档或寻求插件开发者的帮助来解决该问题。

  • d.find_element.textd.find_element.get_attribute('textContent') 都是用于获取元素的文本内容。它们的区别和联系如下:

    1. 区别:

      • d.find_element.text:是 Selenium WebDriver 提供的一种简便方法,用于获取元素的可见文本内容。它会自动忽略元素内部的隐藏文本、注释等内容,只返回用户可见的文本。如果元素包含子元素,将返回所有子元素的文本内容,以及它们之间的空格和换行符。
      • d.find_element.get_attribute('textContent'):是 WebDriver 提供的一种更通用的方法,用于获取元素的文本内容。它可以获取包括隐藏文本在内的所有文本内容,并返回一个字符串。如果元素包含子元素,将返回所有子元素的文本内容,以及它们之间的空格和换行符。
    2. 联系:

      • 都可以用于获取元素的文本内容。
      • 都需要通过 d.find_element 或其他相应方法来定位元素。
      • 都可以通过 WebDriver 库来实现。

    总的来说,d.find_element.text 更适用于获取用户可见的文本内容,而 d.find_element.get_attribute('textContent') 则更适用于获取所有文本内容,包括隐藏文本等。

  • 你可以将公共的代码抽出来封装成一个方法,并将需要传入的 excel 的路径作为参数。接下来我将为你提供一个示例代码,帮助你理解如何处理 all_val 字典。

    首先,你可以定义一个公共方法(例如 execute_testcases)来处理 Excel 数据。这个方法可以接收 excel 文件路径作为参数,然后在其中调用所有需要执行的用例。

    在这个方法中,你可以实例化一个空的字典 all_val,用于保存接口关联所需要的数据。然后,你可以在每个用例的循环中,将需要保存的数据添加到 all_val 字典中。

    接下来,你可以将 all_val 作为参数传递给具体的接口关联方法,以便进行处理。在这个方法中,你可以使用该字典中的数据来执行接口关联的操作。

    下面是一个示例代码,演示了如何处理公共的代码并使用 all_val 字典:

    def execute_testcases(excel_path):
        all_val = {}  # 创建空字典
        testcases = read_excel(excel_path)  # 从 excel 文件中读取用例
    
        for testcase in testcases:
            # 执行每个用例的操作
    
            # 将需要保存的数据添加到 all_val 字典中
            # 在这里实现将数据存入 all_val 的逻辑
    
        # 在所有用例执行完毕后,将 all_val 字典作为参数传递给接口关联方法
        process_interface_association(all_val)
    
    def process_interface_association(all_val):
        # 在这里实现接口关联的操作逻辑
        # 可以使用 all_val 字典中的数据进行处理
        # 将 all_val 中的数据关联到接口中
    
    # 调用公共方法并传入 excel 文件路径
    execute_testcases('path/to/excel')
    

    请根据你的实际需求,在示例代码中添加相应的逻辑,以实现你想要的结果。希望这个示例能帮到你!

  • 要让 Python 识别并执行函数,你可以使用eval()函数或者exec()语句。这两个功能强大但也潜在地危险,因为它们可以执行任意的 Python 代码。确保你从可信任的源头获取函数名和代码,避免安全风险。

    下面是一个示例代码,展示了如何使用re模块和eval()函数来解析 YAML 文件,并执行函数:

    import re
    
    # 假设这是你的 YAML 用例
    yaml_content = """
    - name: 接口测试
      endpoint: /api/xyz
      data:
        phone: ${get_phone()}
    """
    
    # 假设这是你的自定义函数
    def get_phone():
        # 执行获取手机号的逻辑,并返回
        phone_number = "1234567890"
        return phone_number
    
    # 使用正则表达式匹配函数名
    pattern = re.compile(r"\${([\w_]+)\(\)}")
    
    # 替换函数调用为实际的返回值
    def replace_func(match):
        # 获取函数名
        func_name = match.group(1)
    
        # 根据函数名执行函数并返回结果
        result = eval(func_name)  # 使用 eval() 执行函数
    
        return str(result)
    
    # 替换函数调用为实际的返回值
    replaced_yaml = re.sub(pattern, replace_func, yaml_content)
    
    print(replaced_yaml)
    

    在上面的示例中,我们首先定义了一个get_phone()函数来获取手机号。然后,我们使用正则表达式和re模块来匹配${get_phone()}这样的函数调用,并使用eval()函数执行函数并替换调用。

    请注意,上面的示例只是一个简单的示范,你可能需要根据自己的具体需求进行调整。另外,使用eval()函数或者exec()语句需要谨慎,确保只执行可信任的代码。

  • 你可以尝试使用 Python 中的自动模块导入功能来更智能地管理函数的导入。Python 提供了importlib模块,它可以在运行时动态地导入模块。这样你可以根据需要选择性地导入函数,而不需要提前手动导入整个模块。

    下面是一个示例代码,演示了如何使用importlib动态地导入函数:

    import importlib
    
    def execute_function(module_name, function_name, *args):
        try:
            module = importlib.import_module(module_name)
            function = getattr(module, function_name)
            return function(*args)
        except (ImportError, AttributeError):
            # 处理导入错误或函数不存在的情况
            return None
    
    # 以调用模块A中的函数func1为例
    result = execute_function('moduleA', 'func1', arg1, arg2)
    if result is not None:
        # 执行成功
        print(result)
    else:
        # 函数不存在或导入失败
        print("函数不存在或导入失败")
    
    

    在上面的示例中,execute_function函数接受一个模块名、函数名以及所需的参数。它使用importlib.import_module来动态地导入名为module_name的模块,然后使用getattr函数从该模块中获取名为function_name的函数。然后,你可以执行该函数,并处理可能的导入错误和函数不存在的情况。

    通过使用importlib模块,你可以更灵活地处理函数导入,并根据需要选择性地导入特定的函数,而无需手动导入整个模块。

  • 在性能测试面试中,可能会涉及到与 Kubernetes(K8s)和 Kafka 相关的问题。以下是一些可能的问题及其具体答案:

    1. 什么是 Kubernetes(K8s),它的作用是什么?
      Kubernetes 是一个开源的容器编排平台,用于自动化管理和部署容器化应用程序。它的作用是简化容器应用程序的部署、扩展和管理,提供高可用性、弹性和自愈能力。

    2. 请解释一下 Kubernetes 中的 Pod、Node 和 Cluster 之间的关系。
      Pod 是 Kubernetes 中最小的可部署单元,它可以包含一个或多个容器。Node 是 Kubernetes 集群中的工作节点,可以承载多个 Pod。Cluster 是由多个 Node 组成的 Kubernetes 集群,用于管理和调度应用程序的运行。

    3. 你如何在 Kubernetes 中进行横向扩展(水平扩展)?
      在 Kubernetes 中进行横向扩展时,可以通过修改 Deployment 或 ReplicaSet 的副本数来增加或减少 Pod 的数量。Kubernetes 会自动调整副本数,以便在集群中的可用 Node 上创建或销毁 Pod,从而实现横向扩展。

    4. 请介绍一下 Kafka。
      Kafka 是一个分布式流式平台,用于高吞吐量、可持久化的发布和订阅消息。它是一个在多个消费者和生产者之间提供高性能、可扩展和持久性的消息系统。

    5. Kafka 中的 Producer 和 Consumer 角色分别是什么作用?
      Producer 是向 Kafka 发送消息的角色,它将消息发布到一个或多个主题(topics)。Consumer 则是从 Kafka 接收消息的角色,它可以订阅一个或多个主题,并按照一定的规则消费消息。

    6. 性能测试中,你如何针对 Kafka 进行压力测试?
      在针对 Kafka 进行压力测试时,可以采取以下步骤:
      创建一个包含大量消息的主题。
      使用多个生产者向主题发送消息,并增加消息发送速率以模拟高负载情况。
      使用多个消费者从主题消费消息,并检查消费速率和延迟。
      监控 Kafka 集群的指标,如吞吐量、延迟、存储使用情况等,以评估性能和稳定性。
      使用合适的负载生成工具和测试脚本,模拟实际场景,并逐渐增加负载以达到目标性能指标。

    这些问题和答案可以帮助你在性能测试面试中更好地应对与 Kubernetes 和 Kafka 相关的问题。务必根据实际经验和理解进行进一步深入研究,并强调你在实际项目中应用这些技术的经验。

  • 对于公司目前的自动化情况,我了解到你们只有一个测试环境,开发和手工测试都使用这个环境进行调试和测试。目前,你们已经实现了一个初始版本的接口自动化,但是使用的是现有的测试环境中的数据。同时,UI 自动化还处于刚开始阶段。

    针对你提到的问题,接口自动化经常因为数据原因导致接口报错,这可能是由于其他手动功能测试或开发人员在调试过程中修改了数据导致的。为了解决这个问题,我有几个建议可以提供给你:

    1. 数据隔离:考虑将接口自动化所需的数据进行隔离,不与其他手动测试或开发调试共用。可以使用独立的测试数据或创建专用的测试环境,确保接口自动化的数据稳定性。

    2. 数据回滚:在接口自动化执行之前,确保测试环境的数据处于一个可控的状态。可以在每次执行前进行数据备份,并在测试完成后对数据进行还原或回滚,确保测试环境的数据一致性。

    3. 数据监控:建立一个数据监控系统,及时发现和排查数据异常问题。可以通过监控工具或自动化脚本定期检查测试环境中的数据完整性和准确性,确保数据符合预期。

    4. 自动化流水线:考虑构建自动化流水线,将接口自动化集成到持续集成/持续交付 (CI/CD) 流程中。这样可以确保接口自动化在每次代码提交后自动执行,并保持与最新代码的一致性,减少因为数据变化引起的问题。

    当考虑实施上述建议时,以下是一些具体操作步骤,帮助你更好地解决接口自动化中数据相关的问题:

    1. 数据隔离:

      • 创建一个独立的测试数据库或者测试环境,专门用于接口自动化测试。
      • 确保该数据库或环境与其他手动测试或开发调试过程中使用的数据相隔离,不发生冲突。
    2. 数据回滚:

      • 在接口自动化执行之前,首先备份当前的测试环境数据。
      • 执行接口自动化测试,自动化脚本完成后,可以选择还原备份数据或者进行数据回滚,确保测试环境的数据保持一致性。
    3. 数据监控:

      • 设置监控脚本或使用监控工具,定期检查测试环境数据。
      • 编写脚本或利用工具,验证数据的完整性和准确性,发现异常情况,如数据丢失、数据错误等。
      • 在发现异常情况时,及时报警或通知相关人员进行处理和修复。
    4. 自动化流水线:

      • 集成接口自动化脚本到持续集成/持续交付 (CI/CD) 流程中。
      • 在代码仓库中设置触发器,当有新的代码提交时,自动触发接口自动化脚本的执行。
      • 在自动化流水线中,确保每次执行都是在独立的测试环境中进行,使用已备份的数据或回滚至最后一次正常状态的数据。

    通过以上的实施步骤,你可以更好地管理数据,在接口自动化过程中减少由于数据原因导致的报错问题。这样可以提高自动化测试的稳定性和可靠性,同时减少手动干预的需求。

  • 应届生软件测试 at 2023年08月04日

    学习软件测试需要掌握一定的理论知识和实践经验。以下是几个建议,希望对你学好软件测试有所帮助:

    1. 学习基础知识:掌握软件测试的基本概念、原理和常用术语,了解软件开发的生命周期和不同类型的测试方法。你可以通过阅读相关的教材、书籍或参加在线课程来学习基础知识。

    2. 接口自动化测试:学习掌握接口自动化测试是很重要的。了解如何使用自动化测试框架(如 Selenium、RestAssured 等)编写测试脚本,熟悉常用的接口测试工具和技术(如 HTTP、API、JSON 等),并掌握自动化测试的最佳实践。

    3. 性能测试:了解性能测试的基本概念和流程,学习使用性能测试工具(如 JMeter、LoadRunner 等),掌握如何设计和执行负载测试、压力测试和性能调优,能够分析性能测试结果并提出优化建议。

    4. 练习项目:通过实际项目练习来提升技能。可以参与开源项目、参加一些测试驱动的开发活动,或者自己搭建一个小的测试项目进行实践。

    5. 参与社区和活动:加入测试社区,参与测试相关的讨论和交流。可以参加一些测试大会、研讨会或培训课程,与其他测试从业者互动,分享经验和学习资料。

    6. 多做实践和项目:多实践才能真正掌握技能,尝试参与实际项目或实习,亲自进行测试工作,积累经验和技巧。

    7. 持续学习:软件测试是一个不断发展和变化的领域,要持续学习和关注最新的测试技术和趋势。订阅行业博客、参加培训课程,保持对新知识的敏感度和学习动力。

    同时,也建议你在找实习时展现自己的学习热情和实践经验,通过准备简历、面试和技术测试,展示你对软件测试的理解和能力。祝你顺利找到心仪的实习机会!

  • 要实现你描述的场景,可以使用 pytest 的自定义标记(marker)和 pytest-xdist 插件的功能来区分测试用例进行多线程执行。

    首先,你可以在测试类 TestClassA 和 TestClassB 上分别使用不同的自定义标记,比如 "@pytest.mark.a" 和 "@pytest.mark.b":

    import pytest
    
    @pytest.mark.a
    class TestClassA:
        def test_case_a1(self):
            assert 1 + 1 == 2
    
        def test_case_a2(self):
            assert 2 * 2 == 4
    
    @pytest.mark.b
    class TestClassB:
        def test_case_b1(self):
            assert 3 - 1 == 2
    
        def test_case_b2(self):
            assert 4 / 2 == 2
    

    然后,在测试命令中使用 -m 参数来筛选指定的标记,并结合 -n 参数来指定使用的 worker 数量。此时,你可以使用以下命令来启动两个线程分别执行 TestClassA 和 TestClassB 下的用例:

    pytest -n 2 -m a
    pytest -n 2 -m b
    

    第一个命令会启动两个 worker 并执行标记为 @pytest.mark.a 的用例,而第二个命令会启动另外两个 worker 并执行标记为 @pytest.mark.b 的用例。

    这样就可以实现不同的测试类在多线程中并行执行的需求了。请注意确保已安装 pytest-xdist 插件:pip install pytest-xdist

  • 当使用 WinAppDriver+Appium 进行 Windows 桌面应用的自动化测试时,可以使用不同的方式来启动应用。您可以尝试使用 subprocess 或 os.startfile 来启动应用,并使用 webdriver.Remote() 方法创建驱动来进行操作。

    这种方法可以实现对应用程序的操作,但是需要确保应用程序已经启动并且窗口已打开。一些控件可能会在不同的启动方式下有所变化,导致元素定位失败或者控件消失。

    如果登录后的软件界面与手动启动的不一样,可能有以下原因:

    1. 应用启动参数不同:您可能需要检查不同启动方式下应用启动的参数是否一致,确保启动的是相同的应用实例。
    2. UI 自动化的稳定性:不同的启动方式可能会导致应用程序的 UI 展示不一致,控件位置、属性等发生变化,导致自动化测试时无法正确定位元素。

    为了解决这个问题,您可以尝试以下方法:

    1. 确保应用的启动方式一致:不管是手动启动还是通过代码启动,应用的启动方式和参数应该尽量保持一致,以确保一致的测试环境。
    2. 更新元素定位策略:根据应用启动方式的不同,可能需要调整自动化脚本中的元素定位策略,重新定位控件,以适应不同的 UI 展示。

    请注意,使用 WinAppDriver+Appium 进行自动化测试时,有时候会存在一些变化和挑战。您可以根据具体情况,调整测试策略和代码来解决问题。

  • 在 pytest 中,可以使用 fixture 的返回值作为另一个 fixture 的参数化值。可以通过使用 pytest 的 parametrize 装饰器来实现。

    下面是一个示例代码:

    import pytest
    
    @pytest.fixture
    def my_fixture():
        # 返回一个列表作为参数化的值
        return [1, 2, 3]
    
    @pytest.fixture(params=pytest.fixture('my_fixture'))
    def my_parametrized_fixture(request):
        return request.param
    
    def test_my_test(my_parametrized_fixture):
        print(my_parametrized_fixture)
    
    

    在上述示例中,my_fixture 作为一个 fixture,返回了一个列表 [1, 2, 3]。然后,my_parametrized_fixture 使用 params 参数指定为 pytest.fixture('my_fixture'),意味着它的参数值来自于 my_fixture 的返回值。在 test_my_test 中,my_parametrized_fixture 就会被自动作为参数传入。

    注意,在 @pytest.fixture(params=pytest.fixture('my_fixture')) 中,my_fixture 是一个字符串,表示引用 my_fixture 这个 fixture 的返回值。

    这样,每个参数会依次传递给 test_my_test 函数,并执行相应的测试用例。

  • 在使用 JMeter 模拟 MQTT 协议时,连接没有正确断开的问题可能有以下几个原因:

    1. 超时设置不正确:确认连接采样器中设置的超时时间是否足够。如果超时时间过短,可能会导致连接没有正确断开。

    2. 缺少手动断开连接的步骤:确认脚本中是否包含了手动断开连接的步骤。在每次请求结束后,应该显式地执行 MQTT 断开连接的操作。

    3. 使用共享连接:在 MQTT 协议中,可以选择使用共享连接 (Shared Connection) 或独立连接 (Dedicated Connection)。如果使用了共享连接,可能需要手动调用释放连接的操作,确保连接得以正确关闭。

    4. 未正确处理连接的状态:部分连接未断开的原因可能是在脚本中未正确处理连接的状态。比如,在多线程运行并发请求的情况下,可能需要使用合适的同步机制,确保每个线程都正确地断开连接。

    5. 服务端配置问题:连接没有正确断开也可能是由于 MQTT 服务器端的配置问题导致的。请检查服务器的配置文件,确认连接超时等相关参数的设置是否正确。

    综上所述,您可以逐一排查以上几个方面,来定位和解决连接没有断开的问题。

  • ChatGPT-3.5:

    这个问题看起来是由于在加载 JMX 文件时出现了一个 XML 解析异常。具体错误提示是 XML 转换异常,而且在解析C:\Users\Administrator\Desktop**.jmx文件时,出现了一个类转换异常。

    根据错误信息来看,可能是由于 JMeter 遇到了无法预期的结构或类型转换问题导致的。如果你使用的是 JMeter 版本 5.6.2,可能是 JMeter 版本的问题。

    以下是一些解决该问题的步骤:

    1. 检查C:\Users\Administrator\Desktop**.jmx文件是否存在,并确保文件路径正确。

    2. 尝试使用最新版本的 JMeter 来打开该文件,以查看是否存在版本兼容性问题。你可以访问 JMeter 官方网站来下载最新版本:https://jmeter.apache.org/download_jmeter.cgi

    3. 如果在不同的 JMeter 版本之间进行迁移,可以尝试通过手动编辑 JMX 文件来解决问题。你可以使用文本编辑器打开.jmx文件,然后查找并修复与/jmeterTestPlan/hashTree/hashTree/hashTree[10]/hashTree[26]/JSONPostProcessor/hashTree相关的行。你可以尝试删除或替换该行,以解决类型转换问题。

    4. 如果无法通过手动编辑文件解决问题,可以尝试重新创建测试计划并重新配置相关的组件。

    希望这些步骤能帮助你解决问题。如果问题仍然存在,请提供更多信息,例如完整的错误堆栈跟踪和相关的 JMX 文件内容,以便更好地理解和解决问题。

  • 个人还是推荐 minium,源代码问题不大,可以让开发给你开权限,自己下载后,本地就可以用开发者工具写脚本跑了。
    确实不支持 H5,所以 H5 的问题,只能走其他办法。

  • 高考志愿填报问题 at 2023年06月27日

    第一次遇到高中生来社区提问的,欢迎!
    院校层面:首选 211,再看一本,如果 211 实在没有好的专业,或者调剂到垃圾专业的风险极大,可以再考虑降到一本院校选好的专业。

  • 个人感觉,不一定准确

  • 1、商业价值不够大;2、在圈子里的话语权不够高;3、测试圈内部互相排挤打压;4、测试群体在技术体系的建立中不够突出, 被人以研发技术的高低来定价值,脱离了测试本身的价值点

  • 基本要求具备副高以上职称,在本领域有一定的影响力,所在单位是比较知名的企业,尤其是科研院所和高校机构

  • 官方渠道:去找你们省内的 XXX 标准化技术委员会,如果领域匹配,且他们委员会征集新一届的委员,可以提交申请去试试看。
    社会渠道:去找 XXX 协会或者学会这些民间组织机构,可能会有一定的希望入选。

  • 刚去查了一下,你提到的这本书,豆瓣评价极高,对内容讲得更加深入,确实是一本好书

  • python 学习手册 第五版,我觉得最佳

  • 没有单独测试过 js api,为啥要这样测试呢?

  • 这个想法很有意义