AI 驱动的开发者(MEAP)(二)(3)https://developer.aliyun.com/article/1516331
6.3 搜索错误
在这一部分,我们将使用一个基本的(尽管相当牵强)示例来演示我们如何使用 Copilot 来查找和修复我们代码中的问题。 这段代码应该循环遍历整数列表并计算总和。 但是,存在一个“眨眼就会错过”的错误。 总和被赋予了 i 的值,而不是将 i 的值添加到累加总和中。
列表 6.11 简单循环遍历整数列表并计算总和
l = [1, 2, 3, 4, 5] if __name__ == '__main__': sum = 0 for i in l: sum = i print("sum is", sum)
要调试此问题,我们将引入一个新工具:Copilot 实验室。在 Copilot 聊天之前,Copilot 实验室是我们的 IDE 中某些功能可用的唯一方式,具体来说是 VS Code。例如,我们需要使用 Copilot 实验室来查找和修复错误。Copilot 实验室今天仍然具有的主要优势是,它可以访问编辑器窗格中突出显示的内容。此功能使 Copilot 实验室能够直接在您的 IDE 中的可编辑代码上操作。安装扩展到您的 IDE 后,您应该在 IDE 的左侧看到一个 Copilot 实验室工具包。如果您需要提醒如何将扩展安装到您的 IDE 中,请参考附录 A 到 C,其中包含有关安装扩展的说明。
图 6.1 Copilot 实验室工具包菜单,其中包括查找和修复错误的选项。该工具包还提供增强您的代码以及对其进行文档化的功能。
我们将暂时更改 main.py 文件的内容为列表 6.9 中列出的代码。完成此更改后,请突出显示代码,并在 Copilot 实验室工具包中按下“修复 Bug”按钮。您应该会看到类似于图 6.2 中的输出。Copilot 实验室能够确定此代码中的问题,并提供解决此问题的建议。
图 6.2 Copilot 实验室,使用 GPT 模型,已经识别出了错误以及如何解决此错误
或者,我们可以将这段代码复制到 ChatGPT 中,并要求它找到错误。然而,可以争论的是,这可能不太方便,因为在请求 ChatGPT 修复之前,您必须知道代码中存在错误。
6.4 覆盖代码
代码覆盖率是衡量您的代码被测试覆盖程度的一种指标。通常以百分比表示,代表您的代码被测试执行的比例。
代码覆盖率可以用作评估测试效果的指标。如果您的代码覆盖率较低,可能表示您的代码的某些部分未经过测试,这可能导致未捕获的错误和其他问题。另外,如果代码覆盖率高,则您可以放心您的代码经过了充分测试。这并不保证您的代码是无错的,但应该表明对于应该在测试中捕获的错误,您具有很高的信心。
为了确定我们的 Python 项目中的代码覆盖率,我们将使用 coverage
库中提供的代码覆盖率工具 coverage。coverage
库通过对我们的代码进行工具化来收集运行时的覆盖率数据。它可以收集任何 Python 代码的覆盖率数据,包括测试、脚本和模块。通过使用像 coverage 这样的代码覆盖率工具,我们可以更好地了解我们的代码有多少被我们的测试所覆盖,并识别可能需要更多测试的代码区域。
首先,让我们使用 pip 安装 coverage:pip install coverage.
接下来,让我们使用 coverage 运行我们的测试:coverage run -m pytest.
这将运行您的测试并收集覆盖率数据。
接下来,我们将生成一个覆盖率报告。覆盖率报告将显示项目中每个文件的代码覆盖率。我们使用以下命令创建基于文本的覆盖率报告:coverage report
或使用以下命令生成报告的 HTML 版本:coverage html
。报告的 HTML 版本将位于 htmlcov 目录中。图 6.3 显示了覆盖率报告。
图 6.3 代码覆盖率报告显示了我们信息技术资产管理系统项目中每个文件的覆盖情况。
代码覆盖率达到 70% 是一个不错的起点。在现实世界中,我们将继续与我们的团队和生成式人工智能小伙伴合作,将这个指标提高到高 90%。
我们将转向一个新的主题:使用生成式人工智能为我们描述代码列表。
6.5 将代码转换成描述 - 从代码到描述
通常,人们会交给你现有的源代码。确定这段代码的确切入口点、代码的目的以及棕地项目的整体结构可能是具有挑战性的。幸运的是,这正是生成式人工智能真正擅长的领域之一:将代码翻译成文本描述。
首先,我们将把(有 bug 的)代码从上一节复制到 Copilot 聊天对话框中,并加上提示语:“这段代码是做什么的?”
图 6.4 上一节的有 bug 代码,并附有提示,要求 Copilot 聊天向我们解释这段代码。
这个解释引人注目的地方在于,Copilot 聊天检测到代码中存在一个 bug,并提出了如何修复这个 bug 的建议。
图 6.5 Copilot 聊天解释了代码的目的,识别了 bug,并提出了修复建议。
接下来,我们将提出一个更具挑战性的问题。我们将要求它解释我们在本章前面设置的行为测试。将代码复制到 Copilot 聊天窗口中,并询问这个类做什么。您可能会收到类似以下清单的回复。
列表 6.12 Copilot Chat 解释了我们之前编写的行为测试的行为。
The code you provided is not a class but a series of step definitions for a Behavior-Driven Development (BDD) test using the Behave framework in Python. These steps correspond to a specific scenario written in Gherkin language (Given, When, Then format). These step definitions describe how to interact with an AssetManager class and an InMemoryAssetRepository class. They're meant to test the creation of assets and the calculation of the total cost of all assets. Let's go through each step: @given('the Asset Manager is running'): This sets up the initial state for the test. It initializes the AssetManager with an InMemoryAssetRepository (which presumably stores assets in memory during the execution of the test) and a mocked AssetLocationMediator. @given('the InMemoryAssetRepository is initialized') and @given('the AssetLocationMediator is mocked'): These steps appear to be placeholders, as they don't execute any actions. @when('I create an asset with a cost of ${cost}') and @when('I create another asset with a cost of ${cost}'): These steps create Asset instances with the given cost and add them to the AssetManager. @then('the total cost of all assets should be ${total_cost}'): This step retrieves all the assets from the AssetManager, calculates the total cost, and checks that it matches the expected total cost. This sequence of steps allows the user to test the process of creating assets and validating the total cost of all assets in the AssetManager. # Question: What does this method do? # Answer: It gets an asset by its id async def get_asset(self, asset_id: int): asset = self._asset_rest_port.read(asset_id) if asset is None: raise HTTPException(status_code=404, detail="Asset not found") return from_asset(asset)
6.6 从一种语言翻译成另一种语言
看起来很遥远,您可能会发现自己正在查看一个语言只是稍有了解的编程语言编写的代码库。或者,您可能想在当前语言中使用来自另一种语言的特定函数。例如,以 JavaScript 重写 Asset
和 FundingDetails
类会是什么样子呢?好吧,让我们问问 Copilot Chat。
列表 6.13 Copilot Chat 将我们的 Asset
和 FundingDetails
类翻译成了 JavaScript。
class Asset { constructor(id, name, status, category, cost, useful_life, salvage_value, purchase_date, locations, funding_details) { this.id = id; this.name = name; this.status = status; this.category = category; this.cost = cost; this.useful_life = useful_life; this.salvage_value = salvage_value; this.purchase_date = purchase_date; this.locations = locations; this.funding_details = funding_details; } } class FundingDetails { constructor(asset, funding_source, funding_amount) { this.asset = asset; this.funding_source = funding_source; this.funding_amount = funding_amount; } }
我们可能会认为翻译一个或两个类太容易了。好吧,我们将尝试更复杂的事情。如果用 JavaScript 写 AssetManager
行为测试,它会是什么样子?
列表 6.14 Copilot Chat 将我们的 AssetManager
行为测试翻译成了 JavaScript。
const AssetManager = require('./asset_manager'); const Asset = require('./asset'); describe('AssetManager', () => { let assetManager; beforeEach(() => { assetManager = new AssetManager(); }); describe('addAsset', () => { it('should add an asset to the asset list', () => { const asset = new Asset(1, 'Computer', 'Active', 'Hardware', 1000.0, 5, 100.0, new Date(), [], null); assetManager.addAsset(asset); expect(assetManager.assets.length).toBe(1); expect(assetManager.assets[0]).toBe(asset); }); }); describe('removeAsset', () => { it('should remove an asset from the asset list', () => { const asset = new Asset(1, 'Computer', 'Active', 'Hardware', 1000.0, 5, 100.0, new Date(), [], null); assetManager.addAsset(asset); assetManager.removeAsset(asset); expect(assetManager.assets.length).toBe(0); }); }); describe('getAssetById', () => { it('should return the asset with the given ID', () => { const asset1 = new Asset(1, 'Computer', 'Active', 'Hardware', 1000.0, 5, 100.0, new Date(), [], null); const asset2 = new Asset(2, 'Printer', 'Active', 'Hardware', 500.0, 3, 50.0, new Date(), [], null); assetManager.addAsset(asset1); assetManager.addAsset(asset2); const result = assetManager.getAssetById(2); expect(result).toBe(asset2); }); it('should return null if no asset with the given ID is found', () => { const result = assetManager.getAssetById(1); expect(result).toBeNull(); }); }); });
另外,您可能希望让 GPT 以惯用的 JavaScript 重写代码。您只需要说:“用惯用的 JavaScript 编写 AssetManager
。”Copilot Chat 将告诉您它使用 ES6 中引入的 class
语法来定义 AssetManager
类。
列表 6.15 AssetManager
类被以惯用的 JavaScript 重写。
class AssetManager { constructor() { this.assets = []; } addAsset(asset) { this.assets.push(asset); } removeAsset(asset) { const index = this.assets.indexOf(asset); if (index !== -1) { this.assets.splice(index, 1); } } getAssetById(id) { return this.assets.find(asset => asset.id === id) || null; } }
这是 AssetManager
行为测试在 JavaScript 中使用 Jest 测试框架的示例(Copilot Chat 在没有我们的建议的情况下包含了这个):
列表 6.16 Copilot Chat 用惯用的 JavaScript 重写了我们的 AssetManager
测试。
const AssetManager = require('./asset_manager'); const Asset = require('./asset'); describe('AssetManager', () => { let assetManager; beforeEach(() => { assetManager = new AssetManager(); }); describe('addAsset', () => { it('should add an asset to the asset list', () => { const asset = new Asset(1, 'Computer', 'Active', 'Hardware', 1000.0, 5, 100.0, new Date(), [], null); assetManager.addAsset(asset); expect(assetManager.assets.length).toBe(1); expect(assetManager.assets[0]).toBe(asset); }); }); describe('removeAsset', () => { it('should remove an asset from the asset list', () => { const asset = new Asset(1, 'Computer', 'Active', 'Hardware', 1000.0, 5, 100.0, new Date(), [], null); assetManager.addAsset(asset); assetManager.removeAsset(asset); expect(assetManager.assets.length).toBe(0); }); }); describe('getAssetById', () => { it('should return the asset with the given ID', () => { const asset1 = new Asset(1, 'Computer', 'Active', 'Hardware', 1000.0, 5, 100.0, new Date(), [], null); const asset2 = new Asset(2, 'Printer', 'Active', 'Hardware', 500.0, 3, 50.0, new Date(), [], null); assetManager.addAsset(asset1); assetManager.addAsset(asset2); const result = assetManager.getAssetById(2); expect(result).toBe(asset2); }); it('should return null if no asset with the given ID is found', () => { const result = assetManager.getAssetById(1); expect(result).toBeNull(); }); }); });
在此示例中,我们使用 Jest 测试框架为 AssetManager
类定义了一组行为测试。我们使用 describe
库确定测试套件和单个测试。
在每个测试中,我们创建 AssetManager
类的一个实例并对其执行某些操作,例如添加或删除资产。然后,我们使用 expect 函数对 AssetManager
类的行为进行断言,例如检查资产是否已添加到资产列表或资产是否已从资产列表中删除。
请注意,在 JavaScript 中,定义类和构造函数的语法与 Python 略有不同,我们使用 required 函数从各自的模块中导入 AssetManager 和 Asset 类。
最后,为了大结局:我们将开始一个新的 ChatGPT 4.0 会话,并将第三章中的 Mermaid 图表粘贴到其中。请以提示“这是我应用程序的 Mermaid 图表,请用 Golang 实现。”为主导。它将返回用 Golang 渲染的代码。
列表 6.17 Copilot Chat 用惯用的 Golang 重写了我们的 Asset
类。
type Asset struct { id int name string status string category string fundingDetails *FundingDetails } func NewAsset(name, status, category string, fundingDetails *FundingDetails) *Asset { return &Asset{ name: name, status: status, category: category, fundingDetails: fundingDetails, } }
接下来,告诉它切换到 Java。
列表 6.18 Copilot Chat 用惯用的 Java 重写了我们的 Asset
类。
class Asset { private int id; private String name; private String status; private String category; private FundingDetails fundingDetails; public Asset(String name, String status, String category, FundingDetails fundingDetails) { this.name = name; this.status = status; this.category = category; this.fundingDetails = fundingDetails; }
6.7 摘要
- 单元测试:重点测试代码的单个组件或单元,以识别特定单元内的错误和问题。单元测试将是您代码库中数量最多的部分。
- 集成测试:测试软件的不同组件或模块之间的交互,以确保无缝集成并检测通信问题。
- 行为测试:从最终用户的角度测试系统的功能,确保其符合要求和规格。
- 模拟对象:以受控的方式模拟自然对象的行为,对于测试和模拟错误条件非常有用。Mock 对象特别擅长模仿测试运行所需但不在测试范围内的系统的某些部分。例如,如果您的类有一个构造函数参数为数据库,但您不想直接测试数据库,因为数据可能会更改,导致您的测试无法得出结论、不可重复或不确定。
- 圈复杂度:衡量软件模块独立路径的数量,表示复杂性和潜在漏洞。
- Halstead 复杂度度量:根据独特的运算符和操作数评估软件复杂度,提供关于代码大小和认知复杂度的见解。
- 可维护性指数:组合了圈复杂度、代码行数和 Halstead 度量等因素,评估软件的可维护性。
- 代码覆盖率:用于评估测试效果的衡量标准,表示代码被测试的程度以及出现未捕获错误的潜力。通常情况下,覆盖率越高越好。
- 语言熟悉度:需要在一个陌生的编程语言中导航代码或希望在当前语言中使用另一种语言的功能。