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.pswp.cn/web/80740.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/80740.shtml
英文地址,請注明出處:http://en.pswp.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…