Amazon Q 从入门到精通 – 测试与重构

Amazon Q Developer 是亚马逊推出的一个专为专业开发人员设计的人工智能助手,旨在提升代码开发和管理效率。其主要功能包括代码生成、调试、故障排除和安全漏洞扫描,提供一站式代码服务。

众所周知,在软件开发领域,测试代码是软件成功的重要基石。它确保应用程序是可靠的,符合质量标准,并且按预期工作。自动化软件测试有助于及早发现问题和缺陷,减少对最终用户体验和业务的影响。此外,测试本身就是一个最可靠的文档,把每个细分功能进行了明确。同时,它也是一个细化到最小功能单元的安全网,可以防止代码随时间变化而发生回归(Regression)问题。

因此,在现代软件工程实践中,经常会看到书写 100 行功能代码的同时,开发人员会同时书写 1.5 倍甚至更多的测试代码来保证功能的正确性。另外,在知名的 GitHub 开源工程中,当贡献者开启 Pull Request 时,系统就会自动运行开发者自己编写的单元测试程序。单元测试程序的好坏和执行结果,都是评审人重要的审查标准。

在这篇博客文章中,我们将展示如何通过集成像 Amazon Q Developer 这样的智能 GenAI 工具来为单元测试,自动化测试场景快速、准确地生成测试用例,并以一些实际的代码用例,来描述测试的最佳实践原则,以及 Amazon Q 如何能够在其中扮演重要的角色。

不可测试的代码

当我们追求整洁、优雅的代码的同时,像硬币总会有另一面一样,世界上总会存在着混乱,风格怪异,难以测试的“意大利面条”式的代码。

什么是“意大利面条”式的代码呢?如下所示:

class Printer:def __init__(self):self.printer_name = "Default Printer"def print_document(self, content):print(f"Printing with {self.printer_name}: {content}")# 模拟打印操作with open("print_history.log", "a") as f:f.write(f"Printed: {content}\n")class Database:def __init__(self):self.connection = "Database Connection String"def save_data(self, data):print(f"Saving to database: {data}")# 模拟数据库操作return Truedef get_data(self, query):# 模拟从数据库获取数据return f"Data for query: {query}"class ReportGenerator:def __init__(self):# 直接在构造函数中实例化依赖,这是不好的实践self.printer = Printer()self.database = Database()def generate_monthly_report(self, month):# 违反单一职责原则:既处理数据,又负责打印print("Starting report generation...")# 直接访问数据库sales_data = self.database.get_data(f"SELECT * FROM sales WHERE month = {month}")# 直接处理文件with open(f"report_{month}.txt", "w") as f:f.write(f"Sales Report for Month: {month}\n")f.write(str(sales_data))# 直接打印self.printer.print_document(f"Monthly Report - {month}")# 再次访问数据库保存记录self.database.save_data({"report_type": "monthly","month": month,"status": "completed"})def generate_daily_report(self, date):# 类似的混乱逻辑daily_data = self.database.get_data(f"SELECT * FROM daily_sales WHERE date = {date}")# 直接文件操作with open(f"daily_report_{date}.txt", "w") as f:f.write(f"Daily Report for: {date}\n")f.write(str(daily_data))# 直接打印self.printer.print_document(f"Daily Report - {date}")# 保存状态到数据库self.database.save_data({"report_type": "daily","date": date,"status": "completed"})# 使用示例
if __name__ == "__main__":report_gen = ReportGenerator()report_gen.generate_monthly_report("2023-12")report_gen.generate_daily_report("2023-12-26")

这段代码看上去很简单,主对象 report_gen,依赖于 printer,和 database 对象来进行打印和报表保存。甚至为了更快地得到代码所要展现的信息,可以让 Amazon Q 帮你绘制一个文字风格的时序图。如下图所示:

真的很棒!基本都不用看代码,就能知道它在做什么了,这是一个对开发者很实用的功能。

把代码执行一下,它的输入如下图所示。

接下来,让 Amazon Q 来解释一下这段代码,看看它能否找到一些问题?在 Amazon Q Chat 窗口里,输入最关注的问题,如“Can you help me find issues with the code in test.py, from design and testability perspective? don’t give suggestion, just list all of issues.”。Amazon Q 的回复如下图所示。

Amazon Q 很轻松地找到了相关的核心问题,问题不少,但本文只挑选设计和测试方面的问题如下:

  • 紧耦合

-ReportGenerator 直接实例化了 printer和database。

-直接实例化导致 ReportGenerator 无法被隔离。

-因此无法注入 mock 的 printer 和 database 来对 ReportGenerator 进行测试。

  • 违背了单一职责原则

-ReportGenerator 身兼数职,不但要做数据库操作和文件操作,连报表生成也都一起包揽。

-Printer 类里包含了打印和日志两项职能。

-Database 类包含了读写两类操作。

  • 直接依赖外部实体

-Printer 类直接文件操作。

-ReportGenerator 类直接进行文件操作。

-Database 类的直接操作。

  • 缺少接口抽象

-Printer 类没有对应的接口抽象。

-Database 类没有对应的接口抽象。

-组件之间交互时,没有契约。

显然,这种无法做单元测试的代码,不但很难保证质量,维护起来也很麻烦,复用性也很差。

使用 Amazon Q Developer 重构代码

稍微改动一下 Amazon Q 的提示词,允许它给我们提提建议。在 Amazon Q Chat 窗口里,再次输入问题“/dev Can you help me refactor these issues? Please focus on design and testing related issues only”。Amazon Q 的回复如下图所示。

在接受了所有代码变更之后,Amazon Q Developer 为我们创建了一个主程序文件,和一个测试代码文件。

其中,test_report_generator.py 是一个测试代码文件。它的代码如下所示。

import unittest
from unittest.mock import MagicMock, patch
from pathlib import Path
from refactored_test import (ReportGenerator,Printer,Database,ReportWriter,ReportConfig
)class TestReportGenerator(unittest.TestCase):def setUp(self):self.printer = MagicMock()self.database = MagicMock()self.report_writer = MagicMock()self.report_generator = ReportGenerator(printer=self.printer,database=self.database,report_writer=self.report_writer)def test_generate_monthly_report(self):# Arrangemonth = "2023-12"test_data = "Test sales data"self.database.get_data.return_value = test_data# Actself.report_generator.generate_monthly_report(month)# Assertself.database.get_data.assert_called_once()self.report_writer.write_report.assert_called_once()self.printer.print_document.assert_called_once_with(f"Monthly Report - {month}")self.database.save_data.assert_called_once()def test_generate_daily_report(self):# Arrangedate_str = "2023-12-25"test_data = "Test daily data"self.database.get_data.return_value = test_data# Actself.report_generator.generate_daily_report(date_str)# Assertself.database.get_data.assert_called_once()self.report_writer.write_report.assert_called_once()self.printer.print_document.assert_called_once_with(f"Daily Report - {date_str}")self.database.save_data.assert_called_once()class TestReportWriter(unittest.TestCase):def setUp(self):self.test_dir = Path("test_reports")self.config = ReportConfig(output_directory=self.test_dir,log_file=Path("test_logs/print_history.log"))self.report_writer = ReportWriter(self.config)def tearDown(self):# Clean up test filesif self.test_dir.exists():for file in self.test_dir.glob("*"):file.unlink()self.test_dir.rmdir()def test_write_report(self):# Arrangefilename = "test_report.txt"content = "Test report content"# Actself.report_writer.write_report(filename, content)# Assertreport_path = self.test_dir / filenameself.assertTrue(report_path.exists())self.assertEqual(report_path.read_text(), content)if __name__ == '__main__':unittest.main()

主程序文件(refactored_test.py)代码如下。

from abc import ABC, abstractmethod
from typing import Protocol
from dataclasses import dataclass
import os
from datetime import date
from pathlib import Pathclass PrinterInterface(Protocol):def print_document(self, content: str) -> None:...class DatabaseInterface(Protocol):def save_data(self, data: dict) -> bool:...def get_data(self, query: str) -> str:...@dataclass
class ReportConfig:output_directory: Pathlog_file: Pathclass Printer:def __init__(self, name: str = "Default Printer", log_file: Path = Path("print_history.log")):self.printer_name = nameself.log_file = log_filedef print_document(self, content: str) -> None:print(f"Printing with {self.printer_name}: {content}")# Safely handle file operationsself.log_file.parent.mkdir(parents=True, exist_ok=True)with open(self.log_file, "a") as f:f.write(f"Printed: {content}\n")class Database:def __init__(self, connection_string: str = "Database Connection String"):self.connection = connection_stringdef save_data(self, data: dict) -> bool:print(f"Saving to database: {data}")return Truedef get_data(self, query: str) -> str:return f"Data for query: {query}"class ReportWriter:def __init__(self, config: ReportConfig):self.config = configdef write_report(self, filename: str, content: str) -> None:filepath = self.config.output_directory / filenamefilepath.parent.mkdir(parents=True, exist_ok=True)with open(filepath, "w") as f:f.write(content)class ReportGenerator:def __init__(self,printer: PrinterInterface,database: DatabaseInterface,report_writer: ReportWriter):self.printer = printerself.database = databaseself.report_writer = report_writerdef generate_monthly_report(self, month: str) -> None:print("Starting monthly report generation...")# Get datasales_data = self.database.get_data(f"SELECT * FROM sales WHERE month = {month}")# Generate report contentreport_content = self._format_monthly_report(month, sales_data)# Write reportself.report_writer.write_report(f"report_{month}.txt", report_content)# Print reportself.printer.print_document(f"Monthly Report - {month}")# Log completionself._log_report_completion("monthly", month=month)def generate_daily_report(self, date_str: str) -> None:print("Starting daily report generation...")# Get datadaily_data = self.database.get_data(f"SELECT * FROM daily_sales WHERE date = {date_str}")# Generate report contentreport_content = self._format_daily_report(date_str, daily_data)# Write reportself.report_writer.write_report(f"daily_report_{date_str}.txt", report_content)# Print reportself.printer.print_document(f"Daily Report - {date_str}")# Log completionself._log_report_completion("daily", date=date_str)def _format_monthly_report(self, month: str, data: str) -> str:return f"Sales Report for Month: {month}\n{data}"def _format_daily_report(self, date_str: str, data: str) -> str:return f"Daily Report for: {date_str}\n{data}"def _log_report_completion(self, report_type: str, **kwargs) -> None:completion_data = {"report_type": report_type,"status": "completed",**kwargs}self.database.save_data(completion_data)# Example usage:
def create_report_system(output_dir: str = "reports",log_file: str = "logs/print_history.log"
) -> tuple[ReportGenerator, PrinterInterface, DatabaseInterface, ReportWriter]:config = ReportConfig(output_directory=Path(output_dir),log_file=Path(log_file))printer = Printer(log_file=config.log_file)database = Database()report_writer = ReportWriter(config)report_generator = ReportGenerator(printer, database, report_writer)return report_generator, printer, database, report_writer

重构后的代码,主要的变更和好处如下:

  • 定义了接口协议类-PrinterInterface 定义了打印机的接口,而 Printer 是它的一个具体的实现。给予这种设计,可以有更多的实现,比如 pdf 打印机,激光打印机等等。-DatabaseInterface 定义数据库的接口,而 Database 是它的一个具体的实现,基于这种设计,可以有更多的实现,比如内存型数据库、文件型数据库、关系型数据库等等。-可以很容易地升级/替换 Printer 和 Database 的实现代码,而不影响 ReportGenerator 本身的功能。

  • 增加了系统的契约-ReportGenerator 不依赖于具体的实现,而是依赖于契约(接口)-基于接口的设计,可以非常容易地置换为 Mock 的实现,来进行充分的测试。-有了契约,就有了可测试性。

一图胜千言,为了更好地理解重构带来的变化,可以再次让 Amazon Q Developer 来图文结合地进行描述和总结,输入提示词,“Can you show the importance of introducing abstract interface than before in ASCII-style diagram?”,Amazon Q Developer 将用文字版图形来描述重构里引入抽象接口起到的关键作用。

通过简单/直接的自然语言交互,在分钟级别的时间范围内,Amazon Q Developer 便完成了对不良设计的重构,把遵循良好设计的代码呈现在开发者的面前。

快捷的单元测试生成方式

如果开发者当下的任务是节约编写单元测试的精力和时间,除了使用/dev 来进行代码重构外,Amazon Q Developer 提供了专门的/test 命令

打开要编写单元测试的文件,在 Amazon Q Developer 的 Chat 窗口里输入 /test,即可开始编写单元测试代码,如下图所示。

单元测试代码创建中,会显示进度。如下图所示。

最终,和使用/dev 一样,Amazon Q Developer 不会直接变更代码,而是给出一个临时的变更结果给开发者,开发者可以以 diff 的形式进行查看,并决定是接受,还是拒绝。

就是如此简单,开发者就可以完成之前繁琐的创建单元测试的工作。

不仅如此,当业务代码不断随着市场需求发生频繁变化的时候,开发者将可以随时以智能化、自动化的方式,让 Amazon Q Developer 协助生成最新的单元测试代码,让单元测试能够提供精确代码质量保证的同时,不再产生高昂的维护代价!

最后

本文以一个“意大利面条式”的,充满了不良设计的代码为样例,展示了 Amazon Q Developer 如何能够以简单/精炼的自然语言交互的方式,短时间内帮助开发者完成代码重构和自动化测试用例的编写,在确保代码质量的同时,大大降低了测试代码的维护成本。

*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您了解行业前沿技术和发展海外业务选择推介该服务。

本篇作者

本期最新实验为《Agentic AI 帮你做应用 —— 从0到1打造自己的智能番茄钟》

✨ 自然语言玩转命令行,10分钟帮你构建应用,1小时搞定新功能拓展、测试优化、文档注释和部署

💪 免费体验企业级 AI 开发工具,质量+安全全掌控

⏩️[点击进入实验] 即刻开启 AI 开发之旅

构建无限, 探索启程!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/80740.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

专题五:floodfill算法(图像渲染深度优先遍历解析与实现)

以leetcode733题为例 题目解析: 给一个初始坐标(sr,sc)比如示例中的粉色的1,如果周围上下左右都是1,就是连通块(性质相同的地方),把它涂上颜色(2&#xff09…

在金融发展领域,嵌入式主板有什么优点?

在金融发展领域,嵌入式主板能够有力推动金融行业的智能化与高效化进程。主板的强大计算能力可以保障业务高效运行。例如在银行的高频交易场景下,其强大计算能力可确保系统在高负荷下依然保持流畅稳定,快速响应用户需求,大大提升金…

《Python星球日记》 第94天:走近自动化训练平台

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、自动化训练平台简介1. Kubeflow Pipelines2. TensorFlow Extended (TFX)二、自动化训练流程1. 数据预处理2. 模型训练3. 评估与部署三、构建…

PHP、JAVA、Shiro反序列化

目录 一、PHP反序列化 二、JAVA反序列化 三、Shiro反序列化 Shiro-550 反序列化漏洞原理 Shiro-721 反序列化漏洞原理 Padding Oracle 漏洞补充: 防御措施: 一、PHP反序列化 主要是分为有类和无类: 1、有类:就有相关的魔术…

AM32电调学习解读六:main.c文件的函数介绍

最近在学习AM32电调的2.18版本的源码,我用的硬件是AT32F421,整理了部分流程处理,内容的颗粒度是按自己的需要整理的,发出来给有需要的人参考。按自己的理解整理的,技术能力有限,可能理解有误,欢…

WebSocket实时双向通信:从基础到实战

一、WebSocket 基础概念 1. 什么是 WebSocket? 双向通信协议:与 HTTP 的单向请求不同,WebSocket 支持服务端和客户端实时双向通信。 低延迟:适用于聊天室、实时数据推送、在线游戏等场景。 协议标识:ws://&#xff…

【算法】分支限界法和贪心、动态规划、回溯、分治法的区别是

什么是分支限界法 分支限界法是一种用于求解最优化问题的算法,其核心思想是通过剪枝策略减少搜索空间。 分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。 在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就…

[自动化集成] 使用明道云上传附件并在Python后端处理Excel的完整流程

在企业日常自动化场景中,使用低代码平台如明道云搭建前端界面,结合自定义Python后端服务,实现灵活数据处理是一种高效的组合方式。本文将分享一个典型的集成用例:用户通过明道云上传文本和Excel附件,Python后端接收并解析这些信息,最终实现完整的数据处理闭环。 项目背景…

ubuntu下实时检测机械硬盘和固态硬盘温度

sudo apt update sudo apt install smartmontools然后,使用smartctl命令查看硬盘的详细信息,包括温度: sudo smartctl -a /dev/sda实时监控硬盘温度 虽然smartctl不能直接实时显示温度,你可以使用watch命令结合smartctl来定期查…

游戏开发实战(二):Python复刻「崩坏星穹铁道」嗷呜嗷呜事务所---源码级解析该小游戏背后的算法与设计模式【纯原创】

文章目录 奇美拉和队列奇美拉被动技能多对多观察者关系实现自定义元类奇美拉基类 管理奇美拉的队列奇美拉队列类心得体会扩展 规则定义工作相关奇美拉相关 奇美拉属性 在本篇博文,我将介绍本项目的整体框架,以及“编码规则”,这些规则保证了本…

Redis实现分布式锁的进阶版:Redisson实战指南

一、为什么选择Redisson? 在上一篇文章中,我们通过Redis原生命令实现了分布式锁。但在实际生产环境中,这样的基础方案存在三大痛点: 锁续期难题:业务操作超时导致锁提前释放不可重入限制:同一线程无法重复…

大语言模型 12 - 从0开始训练GPT 0.25B参数量 MiniMind2 补充 训练开销 训练步骤 知识蒸馏 LoRA等

写在前面 GPT(Generative Pre-trained Transformer)是目前最广泛应用的大语言模型架构之一,其强大的自然语言理解与生成能力背后,是一个庞大而精细的训练流程。本文将从宏观到微观,系统讲解GPT的训练过程,…

SID 2025上的天马,用“好屏”技术重构产业叙事

作为全球最具影响力的显示行业盛会,SID国际显示周不仅是技术比拼的舞台,更是未来产业方向的风向标。SID 2025上的技术密度与产业动态,再一次验证了这一定律。 Micro-LED、柔性OLED、裸眼3D、量子点、透明显示等新技术在SID 2025集中亮相&…

【AI News | 20250520】每日AI进展

AI Repos 1、nanoDeepResearch nanoDeepResearch 是一个受 ByteDance 的 DeerFlow 项目启发,旨在从零开始构建深度研究代理的后端项目。它不依赖 LangGraph 等现有框架,通过实现一个 ReAct 代理和状态机来模拟 Deep Research 的工作流程。项目主要包含规…

钉钉开发之AI消息和卡片交互开发文档收集

AI消息和卡片交互开发文档 智能交互接口能力介绍 AI助理发消息(主动直接发送模式 AI 助理发消息 - 主动发送模式 AI 助理发消息 - 回复消息模式 AI 助理发消息 - Webhook 回复消息模式 Stream 模式响应卡片回传请求事件 upload-media-files AI 助理发消息&a…

Redis中的事务和原子性

在 Redis 中,事务 和 原子性 是两个关键概念,用于保证多个操作的一致性和可靠性。以下是 Redisson 和 Spring Data Redis 在处理原子性操作时的区别与对比: 1. Redis 的原子性机制 Redis 本身通过以下方式保证原子性: 单线程模型…

Apollo10.0学习——planning模块(8)之scenario、Stage插件详解二

scenario插件 插件总览插件ValetParkingScenario阶段一:StageApproachingParkingSpotprocess()方法 阶段二:StageParkingprocess()方法FinishStage方法 插件PullOverScenarioIsTransferable: 场景切入条件 代码逻辑阶段一:PullOverStageAppro…

JVM的面试相关问题

面试中的相关问题主要是三块 1.JVM 内存区域划分 2.JVM 的类加载机制 3.JVM 的垃圾回收机制 JVM Java虚拟机 VM Virtual Machine 虚拟机,用 软件 来 模拟 硬件 传统意义上的"虚拟机" 更多指的是 VMWare, Virtual Box, Hyper-V, KVM(构造出虚拟的电脑,甚至可以…

win10使用nginx做简单负载均衡测试

一、首先安装Nginx: 官网链接:https://nginx.org/en/download.html 下载完成后,在本地文件中解压。 解压完成之后,打开conf --> nginx.config 文件 1、在 http 里面加入以下代码 upstream GY{#Nginx是如何实现负载均衡的&a…

[特殊字符]车牌识别相机,到底用在哪?

停车场管理,快速通行不是梦 停车场大概是车牌识别相机最常见的 “工作岗位” 啦!以前进出停车场,取卡、刷卡、人工收费,一系列操作下来,高峰期的时候真的能把人等得不耐烦😫 现在有了车牌识别相机&#xff…