龙空技术网

13 使用多步提示语让AI帮你写测试

茶桁 136

前言:

如今大家对“python编写程序输入分钟数”可能比较看重,朋友们都需要知道一些“python编写程序输入分钟数”的相关资讯。那么小编也在网上搜集了一些有关“python编写程序输入分钟数””的相关文章,希望小伙伴们能喜欢,我们一起来了解一下吧!

Hi,大家好,我是茶桁。

很遗憾在上一讲,也就是第12讲的时候,咱们对于利用AI写一个VBA宏来执行Excel任务的过程并不顺利,仔细想来既然大家都在这里看这个系列文章了,应该也基本都会Python的,所以一个Excel自动化也并无太大影响,毕竟,这种商业软件的集成一定是早晚的事情,咱们也不必在这里死磕这一个问题。

那么本节课程呢,我们会通过chatGPT的不断交互,去完成一个测试任务。

在很多时候,我们探索性开发一些功能可以极大提高我们的效率,但是这个过程并不能做成一个完整的产品。我们理想中的产品应该是“自动化”的,我们只需要用自然语言输入自己的需求,对应的代码就自动写出来了。

那么如果中间出现了问题怎么办?当然是AI可以自己拿到反馈自己更正自己了,完全不需要人工去介入调试。

下面,让我们开始吧。

代码的起源

让AI自己调试自己的需求听起来是不是很不可思议?随着GPT-4的发布,还有就是未来模型能力的进一步增强,这个骑士并不是遥不可及。是的,我又在这里贩卖焦虑了,那些低廉的测试们,想要自己的退路了吗?

眼下,我们只有GPT-3.5的API权限。所以我们这一次无法一步到底,目标还是需要低一点,先通过大语言模型,帮助我们写单元测试代码。

整个过程是一个自动档的体验,只是能够提供的能力还相对比较简单,仅限于为现有代码提供单元测试而已。

其实,很早的时候OpenAI官方就在Cookbook中提供了类似的思路和示例,可以参见Unit test writing using a multi-step prompt,不过这个例子里面的代码已经无法使用了,因为对应的code-davinci-002模型已经被OpenAI下线了。但是示例里,分步骤分析问题,通过多个Prompts来完成单元测试的想法,还是完全可以拿来借鉴的。

虽然模型会变,代码也就需要跟着会改变,但是我相信这一课一定能打开你的思路,随着你拿到GPT-4的API乃至未来可能会出现的GPT-5,你都完全可以完成更复杂的“自动写代码”的程序。

先从一个起点开始

为了写测试,我们需要首先编写一个程序。为了避免选择 Leetcode 这样的题库,因为这些题目可能在 AI 的训练数据集中出现并且AI已经知道了答案。因此,我选择了一个有趣的小题目,即让 Python 根据我们输入的一个整数代表的秒数,格式化成一段自然语言描述的时间。例如,输入 1 将返回 1秒,输入 61 将返回 1分钟1秒。为了实现这个功能,我们需要考虑处理输入、分析输入并将其转化为可读的输出。我们可以使用 Python 中的 datetime 模块和字符串处理功能来完成这个任务。我们可以编写单元测试来确保代码的正确性,包括测试各种输入组合的情况,并确保输出符合预期。我们还可以考虑添加错误处理,例如,如果输入不是整数,则返回错误消息。因此,编写测试代码可以帮助我们确保代码的正确性并提高代码的可读性。

让我们从简单需求开始:

咱们用Python写一个函数,进行时间格式化的输出,比如:输入 输出1          1s61        1min1s要求仅需要格式化到小时(?h?min?s)就可以了。

当然,我们是讲这个需求直接抛给chatGPT,让其待我们实现,然后我得到了如下程序:

def format_time(seconds):    hours = seconds // 3600    minutes = (seconds % 3600) // 60    seconds = seconds % 60    time_string = ""    if hours > 0:        time_string += f"{hours}h"    if minutes > 0:        time_string += f"{minutes}min"    if seconds > 0:        time_string += f"{seconds}s"    return time_string

并且,chatGPT还十分贴心的给出了一些示例用法:

现在让我们放在VSCode里尝试一下:

似乎很顺利,没有报错,可以执行,而且目测代码也完成了我们想要的基本功能,使用chatGPT给的示例测试一下看看:

恩,完全没问题。

好了,现在可以让我们完成单元测试的代码了,当然,依然是将问题交给chatGPT:

然后我得到了如下回复:

这里我们需要安装一个新库,在你的命令行内输入:

conda install pytest

将chatGPT给到的代码改动一下,因为毕竟我们是在一个文件内定义的类,并不存在引入的情况:

import pytestdef test_format_time():    # 测试秒数为 1 的情况    assert format_time(1) == "1s"    # 测试秒数为 61 的情况    assert format_time(61) == "1min1s"    # 测试秒数为 3661 的情况    assert format_time(3661) == "1h1min1s"    # 测试秒数为 3600 的情况    assert format_time(3600) == "1h"    # 测试秒数为 0 的情况    assert format_time(0) == ""    # 测试负数秒数的情况    assert format_time(-10) == ""    # 测试较大秒数的情况    assert format_time(123456789) == "34293h21min29s"    # 在此添加更多的测试用例...# 执行单元测试# pytest.main()

好的,我们的单元测试写完了, 下课,咱们下期再见。

。。。

当然是开玩笑的,哪有这么简单。不知道有多少人真的做过程序员或者测试,聪明如你们,当然能从这段代码中看到还存在问题

虽然这个测试考虑到了负数,考虑到了超过24小时较大秒数的情况,但是依然有未考虑到的情况,比如说,我们如果输入了浮点数1.0, 或者字符串abc,在活着Null这样的空值怎么办?虽然前端可以控制输入类型来避免一些情况发生,但是无论如何,我们无法相信前端,并不是因为前端程序员不给力,而是前端是可以被篡改的。我们不知道前端传回来的内容会发生怎样的变化,所以后端校验必须严谨而完整。

下面,让我们继续完善吧,接着我们就要离开WebGPT的交互,转而使用API了,所以请在您的代码内设置好相应的 openai.api_key

分步提示语

我们要明白,就算有了AI,也并不是把问题一股脑的塞给他就可以解决了。我们需要的是反过来自己思考,如果我们自己来为一段代码写单元测试,我们自己会怎么做?

而这些想法,最后就会变成在chatGPT里的Prompts,最终由chatGPT告诉我们答案。

在文章开头我分享的Cookbook里的那个例子里就提供了一份很好的思路,在里面将问题拆成了三个步骤:

把代码提交给大语言模型,让大语言模型解释一下,这个代码是在干什么。这个步骤很重要,因为它可以帮助我们更好地理解代码的含义以及逻辑。如果大语言模型的解释不够详细,我们可以再次提交代码,直到我们完全理解了它的含义。把代码以及代码的解释一起交给大语言模型,让大语言模型规划一下,针对这个代码逻辑,我们到底要写哪几个 TestCase。如果在这个过程里,大语言模型规划的 TestCase 数量太少,那么我们可以重复第二步,让 AI 多生成几个 TestCase。这样可以帮助我们更全面地测试代码,确保代码质量。针对上面生成的 TestCase 的详细描述,我们再次提交给大语言模型,让它根据这些描述生成具体的测试代码。在这个过程中,我们还会对生成的代码进行一次语法检查,如果语法检查没法通过,我们就要让 AI 重新生成一下。这个可以避免因为大语言模型的概率采样不稳定,导致生成的代码无法运行的问题。同时,我们还可以对生成的代码进行一些修改,比如添加注释,让代码更加易读易懂。这个步骤可以帮助我们更好地理解代码的结构,以及代码所要实现的功能。

到最后,我们当然需要实际运行一下这些代码,看看我们的代码是否能够通过这些自动化测试。

自己的代码自己解释

我们将步骤一步步拆解开来,通过Python程序把整个过程“自动化”:

def gpt(prompt, model = 'text-davinci-002', temperature = 0.4, max_tokens = 1000, top_p = 1, stop = ['\n\n', '\n\t\n', '\n   \n']):    response = openai.Completion.create(        model = model,        prompt = prompt,        temperature = temperature,        max_tokens = max_tokens,        top_p = top_p,        stop = stop    )    message = response['choices'][0]['text']    return messagecode = """def format_time(seconds):    hours = seconds // 3600    minutes = (seconds % 3600) // 60    seconds = seconds % 60    time_string = ""    if hours > 0:        time_string += f"{hours}h"    if minutes > 0:        time_string += f"{minutes}min"    if seconds > 0:        time_string += f"{seconds}s"    return time_string"""def explain_code(function_to_test, unit_test_package = 'pytest'):    prompt = f"""# How to write great unit tests with {unit_test_package}In this advanced tutorial for experts, we'll use Python 3.10 and `{unit_test_package}` to write a suite of unit tests to verify the behavior of the following function.```python{function_to_test}```Before writing any unit tests, let's review what each element of the function is doing exactly and what the author's intentions may have been.- First,"""    response = gpt(prompt)    return response, promptcode_explaination, prompt_to_explain_code = explain_code(code)print(code_explaination)

在这一步中,我们所写的代码做了以下几件事情:

首先,我们定义一个gpt的函数,对调用GPT3.5的模型做了一个简单的封装。其中有两点需要特别注意一下:

我们默认使用了 text-davinci-002 模型,这是一个通过监督学习微调的生成文本的模型。因为这里我们希望生成目标明确的文本的代码解释,所以选用了这个模型。我们对 stop 做了特殊的设置,只要连续两个换行或者类似连续两个换行的情况出现,就中止数据的生成。这是避免模型一口气连测试代码也生成出来。那样的话,我们没法对测试代码的生成提出具体的要求。通过 stop,我们可以确保在第一步,只解释现在的功能代码有什么用。此外,我们还对 stop 进行了调优,确保在生成代码解释时不会因为过度使用 stop 而出现信息不完整的情况。具体来说,我们设置了一个阈值,只有当连续两个换行或类似换行的情况出现的次数达到阈值时,才会中止数据的生成。

接下来,我们可以进一步提高代码的解释的准确性。我们可以通过以下几个步骤来实现:

确定使用pytest测试包。提供要测试的代码以及相应的上下文。指示AI对代码的功能进行详细描述。使用“- First”等引导词,引导GPT模型逐步分行描述代码的功能。

这些步骤可以让我们的代码解释更加清晰明了。此外,我们也可以通过提供更详细的上下文和示例来帮助GPT模型对代码的功能进行更准确的描述。例如,我们可以提供更多的测试用例,以确保代码的正确性,并帮助GPT模型更好地理解代码的功能。同时,我们还可以提供更多的注释和解释,以便其他人更好地了解我们的代码。

输出结果:

```python  seconds // 3600  ```  is dividing the number of seconds by 3600 and discarding the remainder. For example, `7200 // 3600` returns `2` because there are `2` hours in `7200` seconds.- Second,  ```python  (seconds % 3600) // 60  ```  is dividing the remainder of the division of `seconds` by `3600` by `60`. For example, `7200 % 3600` returns `0` because there are no seconds remaining after the division by `3600`. Therefore, `(7200 % 3600) // 60` returns `0` because there are no minutes remaining after the division by `60`.- Third,  ```python  seconds % 60  ```  is returning the remainder of the division of `seconds` by `60`. For example, `7200 % 60` returns `0` because there are no seconds remaining after the division by `60`.- Fourth,  ```python  time_string = ""  ```  is initializing an empty string to be used to store the formatted time.- Fifth,  ```python  if hours > 0:  ```  is checking if the number of `hours` is greater than `0`. If it is, the following code will be executed.  ```python  time_string += f"{hours}h"  ```  This code is adding the number of `hours` to the string `time_string` with the `h` character after it. For example, if `hours` is `2`, the string `time_string` will be updated to `"2h"`.- Sixth,  ```python  if minutes > 0:  ```  is checking if the number of `minutes` is greater than `0`. If it is, the following code will be executed.  ```python  time_string += f"{minutes}min"  ```  This code is adding the number of `minutes` to the string `time_string` with the `min` characters after it. For example, if `minutes` is `2`, the string `time_string` will be updated to `"2min"`.- Seventh,  ```python  if seconds > 0:  ```  is checking if the number of `seconds` is greater than `0`. If it is, the following code will be executed.  ```python  time_string += f"{seconds}s"  ```  This code is adding the number of `seconds` to the string `time_string` with the `s` character after it. For example, if `seconds` is `2`, the string `time_string` will be updated to `"2s"`.- Eighth,  ```python  return time_string  ```  is returning the string `time_string`.

运行代码后,AI回复了我们几个步骤,详细秒数了我们格式化时间的代码是如何做的。

自己的解释自己实现

当然接下来,我们就需要根据生成的这个详细描述,请AI为我们制定一下具体的测试计划了:

def generate_a_test_plan(full_code_explaination, unit_test_package="pytest"):    prompt_to_explain_a_plan = f"""A good unit test suite should aim to:- Test the function's behavior for a wide range of possible inputs- Test edge cases that the author may not have foreseen- Take advantage of the features of `{unit_test_package}` to make the tests easy to write and maintain- Be easy to read and understand, with clean code and descriptive names- Be deterministic, so that the tests always pass or fail in the same way`{unit_test_package}` has many convenient features that make it easy to write and maintain unit tests. We'll use them to write unit tests for the function above.For this particular function, we'll want our unit tests to handle the following diverse scenarios (and under each scenario, we include a few examples as sub-bullets):-"""    prompt = full_code_explaination + prompt_to_explain_a_plan    response = gpt(prompt)    return response, prompttest_plan, prompt_to_get_test_plan = generate_a_test_plan(prompt_to_explain_code + code_explaination)print(test_plan)

我们整个测试计划的提示语,同样经过了精心设计。我们首先对 AI 的测试用例做出了以下要求:

在考虑输入范围时,测试用例应尽可能覆盖更广的范围。AI 应考虑到一些边界条件,这些条件可能比代码作者预想的更加复杂。我们希望 AI 能够充分利用 pytest 这个测试包的特性。测试用例应该易于阅读和理解,测试代码应该简洁明了。测试代码的输出结果应该是确定的,要么通过,要么失败,不应该有随机性。

在这一步之后,我们并没有让 AI 立即开始编写测试代码。相反,我们提供了几个例子来让 AI 生成一系列示例。我们对测试用例的提示非常详细,这也是我们之前没有让 AI 直接生成测试用例的原因。因为这种方法无法在提示语中插入这些详细的要求。对于具体的测试用例,我们只能希望 AI 能够自行想出更多的例子。

最后,我们的提示语既包括了第一步要求解释代码内容的要求,也包括了 AI 生成的代码解释的要求,以及我们在这里新增的测试用例要求。这提供了非常详细的上下文,使得 AI 的表现更好,更具有逻辑性。此外,我们建议 AI 参考其他相关测试用例,以确保测试的全面性和正确性。

然后AI输出了结果给我:

The `seconds` input is a positive integer:  - `seconds` is less than 60  - `seconds` is equal to 60  - `seconds` is greater than 60 but less than 3600  - `seconds` is equal to 3600  - `seconds` is greater than 3600- The `seconds` input is a negative integer:  - `seconds` is less than -60  - `seconds` is equal to -60  - `seconds` is greater than -60 but less than -3600  - `seconds` is equal to -3600  - `seconds` is greater than -3600- The `seconds` input is a float:  - `seconds` is less than 0.0  - `seconds` is equal to 0.0  - `seconds` is greater than 0.0 but less than 60.0  - `seconds` is equal to 60.0  - `seconds` is greater than 60.0 but less than 3600.0  - `seconds` is equal to 3600.0  - `seconds` is greater than 3600.0- The `seconds` input is a string:  - `seconds` is an empty string  - `seconds` is a string that can be parsed to an integer  - `seconds` is a string that can be parsed to a float  - `seconds` is a string that cannot be parsed to an integer or a float- The `seconds` input is None:  - `seconds` is None

我运行了一下这个代码,可以看到,AI 提供了很多测试用例。并且,里面考虑了好几种情况,包括我们前面提到的负数这样的特殊条件,也包括输入字符串,以及 None 这样的内容。不仅如此,我们还可以探索更多的情况,例如小数和分数等。此外,我们可以调整代码中的参数,来观察AI生成的测试用例数量和质量。

不过,生成哪些用例其实是有一定的随机性的。这个也是大语言模型的一个缺点,就是可控性差。有时候,AI 可能就只生成了 3 个用例,那样的话就会有很多情况我们的用例覆盖不到。

所以,我们可以在生成用例之后,加一个步骤,检查一下到底生成了多少个用例。如果太少的话,我们就让 AI 再生成一些。我在下面给了一段示例代码,通过“\n-”这样一个换行加横杆的标记来判断之前生成的测试用例数量,如果比我们设定的下限少,我们就再添加一段提示语,让 AI 再生成一些。

这里的提示语,我们要特别提醒 AI 考虑一下测试罕见情况和边界条件,例如极大或极小的输入值,或者一些异常情况的处理。

not_enough_test_plan = """The function is called with a valid number of seconds    - `format_time(1)` should return `"1s"`    - `format_time(59)` should return `"59s"`    - `format_time(60)` should return `"1min"`"""approx_min_cases_to_cover = 7elaboration_needed = test_plan.count("\\n-") +1 < approx_min_cases_to_cover if elaboration_needed:        prompt_to_elaborate_on_the_plan = f"""In addition to the scenarios above, we'll also want to make sure we don't forget to test rare or unexpected edge cases (and under each edge case, we include a few examples as sub-bullets):-"""        more_test_plan, prompt_to_get_test_plan = generate_a_test_plan(prompt_to_explain_code + code_explaination + not_enough_test_plan + prompt_to_elaborate_on_the_plan)        print(more_test_plan)

然后得到结果:

The function is called with a valid number of seconds    - `format_time(1)` should return `"1s"`    - `format_time(59)` should return `"59s"`    - `format_time(60)` should return `"1min"`- The function is called with an invalid number of seconds    - `format_time(-1)` should raise a `ValueError`    - `format_time("60")` should raise a `ValueError`- The function is called with a valid number of seconds and the `hours`, `minutes`, or `seconds` are `0`    - `format_time(0)` should return `""`    - `format_time(3600)` should return `"1h"`    - `format_time(7200)` should return `"2h"`    - `format_time(7201)` should return `"2h1s"`
自己的计划自己生成

当然,有些情况下,生成的测试用例数会比我们的实际情况更少。这时候,我们需要想办法增加测试用例的数量,以便更全面地测试我们的代码。一种方法是增加测试数据的覆盖范围。我们可以通过添加一些边界值、特殊值、无效值等来增加测试用例的数量。

除了增加测试用例的数量,我们还可以增加测试用例的复杂度。这样可以更好地测试代码的鲁棒性和可扩展性。我们可以通过增加测试用例的步骤、条件等来增加测试用例的复杂度。

另外,为了提高测试用例的可读性和可维护性,我们可以将测试用例分为不同的类别,并为每个类别定义一个清晰的目标。例如,我们可以将测试用例按照输入数据的类型、函数的不同参数组合、不同的执行路径等进行分类。

对于这些分类,我们可以在提示语中指明要测试的具体内容,以帮助 AI 编写更加精确、全面的测试用例。同时,我们还可以提供一些代码示例或者代码注释来帮助 AI 理解我们要测试的功能代码。

需要注意的是,我们在生成提示语的时候,要尽可能保留原有的关键信息,以确保 AI 编写的测试用例符合我们的测试要求。

def generate_test_cases(function_to_test, unit_test_package="pytest"):    starter_comment = "Below, each test case is represented by a tuple passed to the @pytest.mark.parametrize decorator"    prompt_to_generate_the_unit_test = f"""Before going into the individual tests, let's first look at the complete suite of unit tests as a cohesive whole. We've added helpful comments to explain what each line does.```pythonimport {unit_test_package}  # used for our unit tests{function_to_test}#{starter_comment}"""    full_unit_test_prompt = prompt_to_explain_code + code_explaination + test_plan + prompt_to_generate_the_unit_test    return gpt(model="text-davinci-003", prompt=full_unit_test_prompt, stop="```"), prompt_to_generate_the_unit_testunit_test_response, prompt_to_generate_the_unit_test = generate_test_cases(code)print(unit_test_response)

再次,AI为我生成了如下内容:

#The first element of the tuple is a string that describes the test case,#and the second element is the input for the format_time function.@pytest.mark.parametrize("test_case, seconds", [    # Positive integer tests    ("Less than 60", 59),    ("Equal to 60", 60),    ("Greater than 60 but less than 3600", 7200),    ("Equal to 3600", 3600),    ("Greater than 3600", 7201),    # Negative integer tests    ("Less than -60", -59),    ("Equal to -60", -60),    ("Greater than -60 but less than -3600", -7200),    ("Equal to -3600", -3600),    ("Greater than -3600", -7201),    # Float tests    ("Less than 0.0", -1.0),    ("Equal to 0.0", 0.0),    ("Greater than 0.0 but less than 60.0", 59.9),    ("Equal to 60.0", 60.0),    ("Greater than 60.0 but less than 3600.0", 7200.0),    ("Equal to 3600.0", 3600.0),    ("Greater than 3600.0", 7201.0),    # String tests    ("Empty string", ""),    ("String that can be parsed to an integer", "7200"),    ("String that can be parsed to a float", "7200.0"),    ("String that cannot be parsed to an integer or a float", "7200.0.0"),    # None test    ("None", None)])def test_format_time(test_case, seconds):    # This test checks that the output of the format_time function    # is the expected output for the given input.    expected_output = ""    if seconds is not None:        hours = seconds // 3600        minutes = (seconds % 3600) // 60        seconds = seconds % 60        if hours > 0:            expected_output += f"{hours}h"        if minutes > 0:            expected_output += f"{minutes}min"        if seconds > 0:            expected_output += f"{seconds}s"    assert format_time(seconds) == expected_output

在运行这段提示语之后,我们可以得到最终输出的测试代码。除了正常情况下的测试,还包括异常输入的测试。如果输入错误,代码也会输出警告信息。这样,我们可以确保代码在各种情况下都能够正常工作。同时,我们也可以添加更多的测试用例,以覆盖更多的情况,从而提高代码的质量和可靠性。因此,如果您需要进行测试,可以使用这个测试代码作为起点,随时添加新的测试用例。这将有助于确保您的代码在各种情况下都能够正常运行。

最后还缺少了什么?当然是语法检查。

通过 AST 库进行语法检查

我们建议使用Python的AST库再次检查生成的测试代码的语法。这样我们可以确保我们的测试代码是具有正确语法的。需要注意的是,在检查语法时,我们不仅需要生成的测试代码,还需要原始的功能代码。这样才能通过语法检查。另外,为了更好地测试代码,您可以考虑添加一些额外的测试用例,以确保代码的正确性和稳定性。

import astcode_start_index = prompt_to_generate_the_unit_test.find("```python\\n") + len("```python\\n")code_output = prompt_to_generate_the_unit_test[code_start_index:] + unit_test_responsetry:    ast.parse(code_output)except SyntaxError as e:    print(f"Syntax error in generated code: {e}")

非常幸运,直接通过了语法检查。下一步,我们把对应的整个测试代码打印出来执行试试:

print(code_output)

输出结果最后AI帮我们生成的测试代码:

import pytest  # used for our unit testsdef format_time(seconds):    hours = seconds // 3600    minutes = (seconds % 3600) // 60    seconds = seconds % 60    time_string = ""    if hours > 0:        time_string += f"{hours}h"    if minutes > 0:        time_string += f"{minutes}min"    if seconds > 0:        time_string += f"{seconds}s"    return time_string#Below, each test case is represented by a tuple passed to the @pytest.mark.parametrize decorator.#The first element of the tuple is a string that describes the test case,#and the second element is the input for the format_time function.@pytest.mark.parametrize("test_case, seconds", [    # Positive integer tests    ("Less than 60", 59),    ("Equal to 60", 60),    ("Greater than 60 but less than 3600", 7200),    ("Equal to 3600", 3600),    ("Greater than 3600", 7201),    # Negative integer tests    ("Less than -60", -59),    ("Equal to -60", -60),    ("Greater than -60 but less than -3600", -7200),    ("Equal to -3600", -3600),    ("Greater than -3600", -7201),    # Float tests    ("Less than 0.0", -1.0),    ("Equal to 0.0", 0.0),    ("Greater than 0.0 but less than 60.0", 59.9),    ("Equal to 60.0", 60.0),    ("Greater than 60.0 but less than 3600.0", 7200.0),    ("Equal to 3600.0", 3600.0),    ("Greater than 3600.0", 7201.0),    # String tests    ("Empty string", ""),    ("String that can be parsed to an integer", "7200"),    ("String that can be parsed to a float", "7200.0"),    ("String that cannot be parsed to an integer or a float", "7200.0.0"),    # None test    ("None", None)])def test_format_time(test_case, seconds):    # This test checks that the output of the format_time function    # is the expected output for the given input.    expected_output = ""    if seconds is not None:        hours = seconds // 3600        minutes = (seconds % 3600) // 60        seconds = seconds % 60        if hours > 0:            expected_output += f"{hours}h"        if minutes > 0:            expected_output += f"{minutes}min"        if seconds > 0:            expected_output += f"{seconds}s"    assert format_time(seconds) == expected_output
抓个BUG试试

我们可以试着在 Notebook 里面调用一下 format_time(-1),看看自动化测试跑得对不对。

如图可以看到,输入-1的时候,输出变成了59min59s, 确实AI生成的测试代码帮我们捕捉到了一个Bug。

小结

好了,到这里这一讲也就结束了。我们不仅学会了如何利用一个方法,将一个问题拆分成多个提示语的步骤,循序渐进地让 AI 通过解释代码,构造测试用例,最后再根据代码的解释和设计的测试用例,生成最终的自动化测试,而且还学会了如何在这个过程中,增加更多的内容,以达到更全面的测试。

在生成整套测试代码的过程中,我们不需要人工地复制粘帖任何内容,全都是代码自动完成的,是一个“自动档”的过程。通过将一个问题拆分成多个提示语的步骤,我们的内容变得更加有条理、有逻辑,也更符合我们平时写文字的方式,而不是一股脑地把各种要求都放在提示语的开头,这在解决复杂问题时往往效果不好。

此外,我们还学会了使用多步提示语的好处。多步提示语带来的一个好处,就是能让 AI 考虑各种边界条件。在得到代码的解释之后,我们可以让 AI 考虑 -1、None 这样的特殊输入,从而涵盖更多的测试情况。这样,我们的测试代码最终真的抓住了程序里的 Bug。

回过头来看,如果我们只是直接把代码往 ChatGPT 里一贴,虽然也能生成测试用例,但是那些测试用例就比较欠考虑,不会涵盖各种边角的情况。因此,我们在生成测试用例的过程中,应该尽可能地提供更多的提示语,让 AI 的测试代码更加全面和详尽。

思考题

在本讲中,代码内容有点长,思考题部分需要你思考的内容更多。

你可以试着减少我们的提示语或者提示步骤,看看生成的测试用例有什么样的变化。你可以尝试移除一些提示语,或者更换提示步骤的顺序,从而得到不同的测试结果。目前我们的代码是以过程式方式一步步演示整个测试代码是如何生成的。如果语法检查出错了,我们实际上应该从头开始重试一遍,再次生成测试代码。你可以尝试将整个代码封装修改,变成一个会自动重试 3 次的函数。这样,我们就可以直接调用这个函数,为 Python 代码生成自动化测试了。我们本讲中的提示语是借鉴了 OpenAI Cookbook 的样例。你可以尝试总结一下,这些提示语中有哪些常用的方法是值得借鉴的。

欢迎你将你的思考结果分享在评论区,同时也欢迎你将本讲分享给感兴趣的朋友。我们下一讲再见。

推荐阅读

我们之所以要循序渐进地提示 AI,让 AI 先生成例子再生成代码,是因为现在的大型语言模型具有一种名为“思维链(CoT)”的能力。当我们提供更详细的推理步骤时,AI 的表现会更好。在 OpenAI Cookbook 中,有一章专门介绍了思维链的能力,你可以去仔细研读一下。

标签: #python编写程序输入分钟数