恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

提示工程+LoRA微调:让大模型生成可直接进CI的Java单元测试

  • 首页
  • 资讯中心
  • /
  • 提示工程+LoRA微调:让大模型生成可直接进CI的Java单元测试

相关资讯

Windows装Redis全攻略:从MSI安装到配置调优与故障排查 2026/10/3 2:56:33
西储大学轴承数据集故障诊断平台(Windows本地版) 2026/10/3 2:56:33
MuJoCo+PPO实战:Ant/Hopper/Humanoid稳定训练全指南 2026/10/3 2:56:33

最新资讯

类型安全容器设计:告别Map<String,Object>与ClassCastException
AI时代设计师如何避免成为算力耗材?从模型原理到工作流重构
MATLAB仿真PID参数整定:从建模到指标优化,告别手动调试
Hermes Agent工业级落地:Windows多Agent协同工程实践
Flutter跨平台鸿蒙开发实战:手账便签收藏应用的技术取舍
函数组合:Haskell高阶编程范式与软件架构的核心

今日推荐

SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成
编译原理实验:递归下降分析器消除左递归与避坑指南
Python协议级爬取Shopee商品数据实战

本周热门

从像素到笔画:srt-whiteboard-animation骨架笔迹追踪实现(Zhang-Suen细化+8邻接追踪)
网站建设的英语怎么说?别只背单词,看完这套安全完整流程才敢上线
新手入门看这篇:建设网站加盟避坑指南与SEO实操

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

提示工程+LoRA微调:让大模型生成可直接进CI的Java单元测试

发布时间:2026/10/3 2:56:33
提示工程+LoRA微调:让大模型生成可直接进CI的Java单元测试 1. 项目概述1.1 为什么想做这个项目说实话让大语言模型帮你写单元测试用例这件事听起来很爽但真正做起来全是细节。我在维护一个中大型Java服务时每天最烦的就是写那些重复的测试代码构造函数塞参数、Mock依赖、断言返回结果。一个逻辑稍微复杂一点的方法测试代码量比业务代码还多写起来又无聊又容易漏分支。到了迭代后期测试覆盖率看起来不低但很多断言其实形同虚设。后来我开始尝试用提示工程让大语言模型生成单元测试用例跑了几个版本之后发现通用模型直接生成的测试代码有两个致命问题一是大量生成“毕设风格”的测试只顾着让代码跑通根本没有验证业务逻辑二是断言写得像挠痒痒mock了所有依赖之后测了个寂寞。于是就有了这个项目的完整思路先通过精心的提示设计拿到可用的基线结果再用这些结果构造训练数据通过LoRA微调让模型真正理解这个项目的测试规范最终达到能直接进入CI的测试生成效果。这个项目适合谁如果你的团队正在推单元测试但写测试消耗的工时已经让你肉疼如果你试过拿ChatGPT、Copilot生成测试但不得不花大量时间修修改改或者你正在做LLM应用开发想搞清楚如何把提示工程和微调这两套手段结合起来那这篇东西能省掉你不少自己摸索的时间。1.2 最终实现了什么效果先给结论整个方案落地后模型生成的测试用例在我们的Spring Boot项目上达到了83%的Line Coverage、71%的Branch Coverage比直接用通用模型提示生成提升了大约20个百分点。更关键的是生成的测试代码里直接能用的比例不需要人工修改就能通过编译、通过断言从原来的26%提升到了74%。这里面的核心手段就是先做提示工程拿到高质量训练样本再做LoRA微调把“这个项目的测试规范”烧进模型参数里。本篇文章会从思路拆解、提示设计、微调实现、问题排查四个部分讲清楚整个链路的做法中间会穿插大量我已经踩过的坑那些训练框架报错、梯度爆炸、过拟合的问题就不需要你再踩一遍了。2. 提示设计与数据构造高质量基线从哪来2.1 提示词模板的迭代历程很多人拿到这个项目的第一个动作就是写一段提示词让模型生成测试用例。但真做起来你会发现通用模型的提示词里藏着很多隐含问题。第一次我给的提示词大概长这样请为下面的方法生成单元测试 public Order createOrder(Long userId, ListLong skuIds) { ... }结果模型返回的测试基本是三层结构把方法调一次、断言不为null、结束。这种测试拿到了CI里跑一遍全绿但你把业务逻辑里的空指针、库存校验、金额计算全部删掉测试照样绿。所以提示设计的第一原则是让模型知道“什么是有价值的测试”而不只是“怎么调这个方法”。我迭代到第三版之后提示词的结构调整成了几个固定的部分角色设定、任务描述、输入信息、约束要求、输出格式。下面是我最终稳定使用的模板结构Java项目版你是一名资深的Java单元测试工程师擅长JUnit 5和Mockito。 请根据以下源代码、被测方法和测试规范为该类的指定方法生成完整的单元测试类。 【被测类】 {classSourceCode} 【被测方法】 {methodSignatureAndBody} 【依赖关系】 {relevantDependencies} 【项目测试规范】 - 必须使用JUnit 5断言使用AssertJ - Mock外部依赖Redis、数据库、远程接口不要启动Spring上下文 - 测试命名格式方法名_场景_预期结果 - 每个测试必须包含核心行为断言不允许只调用方法后断言非空 - 必须覆盖以下分支正常分支、异常分支、边界值、依赖异常 【输出格式】 只输出完整的Java测试类代码使用java 代码块包裹不要输出多余解释。你可能注意到我把“项目测试规范”单独拉了一块这非常关键。通用模型不懂你的项目用什么断言库、要不要起Spring上下文、命名规则是什么。如果你不说它就会按自己训练数据里最主流的风格来大概率是JUnit 4加Spring Boot Test启动一个完整的MockMvc甚至调Database。这种测试生成出来你光修编译错误就得花半天。我建议提示词里把“依赖关系”也放进去。并不是所有被测方法都能从代码上下文里看出来依赖是什么比如某个Service注入了RedisTemplate、RestTemplate、ObjectMapper你得把这些依赖的类型和Mock策略写清楚否则模型会胡猜生成一堆不存在的Bean。2.2 提示结果筛选与样本质量基线的提示词设计出来之后下一个动作是跑一批结果然后人工筛选把“可用的测试”挑出来。我一开始直接把模型生成的结果全部塞进训练集结果微调之后发现在过拟合的同时还学到了坏习惯。后来想明白了微调阶段模型学的是你给它的数据分布如果你把那些低质量、风格混乱的测试也喂进去它只会生成更多低质量测试。筛选样本的时候我总结了几个硬性标准测试代码能直接编译通过或者只需要极少量修改。至少有一个断言验证了方法的核心行为而不是只验证“不为null”或“不抛异常”。能够体现出项目特有的测试风格比如使用了项目自定义的测试工具类或Mock策略。覆盖到了输入边界或异常分支而不是只有一条happy path。筛选之后我保留了大概800条高质量测试用例然后做了格式归一化把断言从JUnit自带的Assert换成AssertJ的链式断言、把命名统一成“方法名_场景_预期结果”的格式、把Mockito的when/then改成BDD风格的given/willReturn。这一步看起来机械但对微调效果的影响是决定性的。模型在训练时看到的一致性越高生成时的稳定性就越好。数据格式我采用JSONL每行一条样本基本结构是{instruction: 为以下方法生成单元测试..., input: 方法签名与源代码..., output: 完整测试类代码...}这种Alpaca风格的数据格式在LoRA微调中非常通用不过你需要注意你的base model用什么格式训练过最好匹配它的chat模板否则指令遵循效果会打折扣。我用的Qwen基座模型train的时候就把指令拼接成它的chat格式这一点后面讲微调参数时还会细说。2.3 为什么先做提示工程再做微调这块我觉得值得单独拎出来说因为很多做微调的团队犯的最大错误就是跳过提示工程直接微调。微调的本质是“在你指定的分布上继续训练”它解决的是模型不知道你的偏好、不知道你的规范的问题。但如果连你自己都说不清楚“想要什么样的测试”那你怎么构造训练集你靠什么标准来筛选sft数据提示工程的作用本质上是一个“逼你把需求说清楚”的过程。我通过反复调整提示词把自己对测试代码的偏好梳理成了可以写下来的规则这些规则最后变成了微调数据的挑选标准和输出格式参考。另外基座模型在提示工程下能拿到什么水平的结果直接决定了微调的下限。如果你的提示词给力但模型生成还是乱七八糟那你微调是对的如果你提示词本身太弱你喂进去的数据质量大概率也一般微调出来的模型只会稳定地输出烂代码。从成本和收益角度核算一下提示工程几乎零成本你只需要花时间调prompt这部分投入是完全可复用的之后换了模型、换了代码库提示词微调一下就能接着用。LoRA微调则需要GPU时间、数据标注成本、评估成本。所以正确姿势永远是先用提示工程把基线打到一个“可以手工筛选出足量干净数据”的程度再做微调。3. LoRA微调把测试风格烧进参数里3.1 基座模型选择与硬件考量LoRA微调的基座模型选择我经历了一个试探过程。一开始想省钱用的6B模型后来发现虽然LoRA训练跑得动但生成的测试代码复杂度一高就开始语法混乱多写几个lambda表达式就漏右括号。后来换了7B和14B的Qwen系列效果提升非常明显。如果你在本地部署大语言模型做微调并且算力有限7B基本是甜点大小质量够用、显存还能撑得住。我最终用的是Qwen2.5-7B-Instruct说实话在代码生成领域它不是最强的Codellama和DeepSeek-Coder更偏向代码补全但它的指令遵循能力在同尺寸里非常出色而且中文和英文都支持得很好。另外还需要考虑一点LoRA微调之后这个模型还要继续承担日常的代码生成任务Qwen在通用场景下的退化比专门的代码模型小。硬件这块我使用的是单张RTX 4090 24GB用QLoRA4-bit量化训练7B模型训练时显存占用大概在16GB左右batch size开到4梯度累积步数设到8等效batch size就是32。如果你的卡是24GB以下我建议要么换更小的基座模型要么把max_seq_len压到2048。训练过程中的一些硬参数我列个表供参考参数设定值说明base_modelQwen2.5-7B-Instruct指令遵循能力强、中文支持好lora_rank16较低维度防止过拟合lora_alpha32缩放系数rank的2倍lora_dropout0.05防止过拟合target_modulesq_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj全量线性层注入LoRAlearning_rate2e-4比全参微调高LoRA推荐范围batch_size4配合梯度累积达到32max_seq_len4096覆盖长测试类避免截断epochs3数据量800条3轮足够fp16True降低显存占用3.2 训练数据清洗与格式转换训练数据的准备工作比很多人想象中要麻烦。从代码仓库里抽取方法、生成测试、清洗每一步都有坑。我这里给出一个我最终跑通的pipeline供参考。第一步是抽取被测方法。我用的是JavaParser把项目里的源代码解析成AST然后按照规则提取每个public方法方法签名、方法体、类名、依赖字段。这里有个细节提取的时候要把“和这个方法相关的上下文”一起提取出来比如方法依赖的类字段、私有辅助方法。否则模型看到的被测方法不完整生成的测试容易用一些不存在的依赖。第二步是生成测试。用稳定的提示词模板调用Qwen的API批量跑。这里特别需要控制temperature我设定的是0.7出来的结果多样性比较好又不至于太飘。如果设为0同一个方法多次生成的结果几乎一样不利于后续人工筛选多样性。第三步是人机协同清洗。让模型先筛一遍候选测试再人工检查。我的标注团队其实就是我自己加一个实习生会把明显不过关的样本删掉剩下的做小修改。这个环节很枯燥但它是整个微调效果的上限来源。最终我构造了800条训练样本和100条验证样本测试类长度的中位数大概在50行左右。清洗完成后把数据转换成模型chat模板需要的格式。Qwen的chat模板格式是|im_start|system 你是一名资深的Java单元测试工程师...|im_end| |im_start|user 为以下方法生成单元测试... {method_code}|im_end| |im_start|assistant java ... 测试代码 ... |im_end|如果你用其他模型一定要去查它的chat templateHuggingFace的tokenizer_config.json里有。这个细节如果搞错了模型训练的时候不会报错但推理时指令遵循会很差效果降一大截。3.3 微调训练过程中的坑LoRA微调听起来轻松实际上调试过程也是有不少陷阱。第一个坑就是梯度爆炸。我在第一次训练时loss在前几百步就冲到了NaN后来发现是learning_rate太高了。LoRA虽然是对低秩矩阵做微调学习率一般可以比全参微调大一些但2e-4这个值在7B模型上运行是稳妥的超过5e-4我就开始遇到loss不稳定。第二个坑是过拟合。800条样本看着不多但如果你用QLoRA跑5个epoch以上验证loss一般会在第3个epoch之后开始反弹。我在训练时专门观察验证loss发现第3轮之后验证loss不降反升果断在epoch3的位置提前停了。第三个坑是我没想到的训练数据里混入了“模型自己生成的代码”与“真实验证过的测试代码”的分布偏移。因为我生成测试时用的是Qwen的API而代码仓库里的真实风格和Qwen的生成风格不完全一致导致训练出来的模型在输出格式上过于“Qwen化”一些琐碎的注释、无用的空行看起来很明显。这个问题的解法是清洗数据时手动删掉那些过于模板化的多余表达越干净越好。训练命令我用的HuggingFace的transformers加peft库整体脚本如下from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments from peft import LoraConfig, get_peft_model from datasets import load_dataset import torch model_name Qwen/Qwen2.5-7B-Instruct tokenizer AutoTokenizer.from_pretrained(model_name, trust_remote_codeTrue) tokenizer.pad_token tokenizer.eos_token model AutoModelForCausalLM.from_pretrained( model_name, load_in_4bitTrue, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) lora_config LoraConfig( r16, lora_alpha32, lora_dropout0.05, biasnone, task_typeCAUSAL_LM, target_modules[q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj] ) model get_peft_model(model, lora_config) dataset load_dataset(json, data_filestrain.jsonl) def format_sample(example): system_prompt 你是一名资深的Java单元测试工程师擅长JUnit 5和Mockito。 user_prompt f为以下方法生成单元测试\n{example[input]} assistant_prompt example[output] text f|im_start|system\n{system_prompt}|im_end|\n|im_start|user\n{user_prompt}|im_end|\n|im_start|assistant\n{assistant_prompt}|im_end| return {text: text} dataset dataset.map(format_sample) def tokenize_function(examples): return tokenizer(examples[text], truncationTrue, max_length4096, paddingFalse) tokenized_dataset dataset.map(tokenize_function, remove_columns[text]) training_args TrainingArguments( output_dir./lora_testgen, per_device_train_batch_size4, gradient_accumulation_steps8, learning_rate2e-4, num_train_epochs3, logging_steps10, save_steps200, eval_steps200, evaluation_strategysteps, fp16True, report_totensorboard, save_total_limit2, remove_unused_columnsFalse, ) import transformers trainer transformers.Trainer( modelmodel, argstraining_args, train_datasettokenized_dataset[train], eval_datasettokenized_dataset[validation], tokenizertokenizer, ) trainer.train()训练完合并LoRA权重from peft import PeftModel base_model AutoModelForCausalLM.from_pretrained(model_name, torch_dtypetorch.float16, device_mapauto) lora_model PeftModel.from_pretrained(base_model, ./lora_testgen/checkpoint-600) merged_model lora_model.merge_and_unload() merged_model.save_pretrained(./lora_testgen_merged)4. 效果评估与回归分析4.1 评估指标不要只看Code Coverage很多人评估“AI生成的测试好不好”只看代码覆盖率这是个巨大的陷阱。代码覆盖率这个指标有个致命的问题它衡量的是“哪些代码被执行了”而不是“哪些行为被验证了”。一段测试如果只是把所有分支都锤了一遍然后什么都不断言覆盖率一样好看但bug照样漏出去。我的评估体系由三个维度组成可直接使用率模型生成的测试代码能直接通过编译、通过断言、不需要人工修改的比例。有效断言率每个测试方法里“有实际验证价值的断言”占总断言的比例。无价值断言指那些只验证非null、非空、调用次数之类的“凑数”断言。Bug检测率往被测代码里人为注入若干个真实缺陷比如把金额校验去掉、把库存扣减条件翻转看测试能抓住几个。我拿这三个维度分别对比了“纯提示工程基线”和“提示工程LoRA微调”的模型结果如下评估维度基线纯提示工程LoRA微调后可直接使用率26%74%有效断言率38%67%Bug检测率35%61%Line Coverage61%83%Branch Coverage52%71%看完数据你会发现LoRA微调的提升是全方位的但最香的其实是可直接使用率。这个指标直接决定了你省了多少人工工时。原来生成10个测试你要改7个现在生成10个只用改3个甚至不需要改。这才是这个项目真正值的价值。4.2 人工审计哪些类型的测试生成得好哪些不行评估不能只看数字还得拆到具体类型看看模型的强项和短板。我把项目里的方法按类型分了四类纯计算型方法、CRUD型方法、外部依赖型方法、复杂业务编排型方法分别测了一遍生成效果。纯计算型方法比如金额计算、日期格式化生成效果是最好的LoRA微调后的模型几乎完美输入输出明确、断言精确、边界值覆盖到位。这种方法的本质是“输入-输出映射”LLM在这个场景下非常擅长只要提示词里把计算规则描述清楚就行。CRUD型方法生成效果次之。这类方法需要Mock Mapper或Repository生成的测试虽然能跑但经常会漏掉“数据不存在”这种分支。不过经过微调之后我发现在训练数据里专门加了几个“空结果”的用例模型就学会这个套路了。外部依赖型方法比如调用第三方API生成效果一般。主要问题在于模型不知道第三方服务的异常行为它倾向于只模拟正常返回和超时两种情况对于“返回错误码但HTTP 200”这种情况抓瞎。这里我还没找到特别好的解法目前的做法是在提示词里把依赖异常场景写得更具体一些。复杂业务编排型方法生成效果最差但这也是预期内的因为这类方法往往涉及事务、多表操作、状态流转甚至连人肉写测试都很容易漏。我目前的建议是这类方法不要完全依靠LLM生成把它当成“人机协作”的重点对象让模型生成骨架人工补足关键分支。4.3 微调模型的本地部署与使用微调完的模型不能只躺在训练日志里要真正跑起来才有价值。我用vLLM部署了一个本地推理服务加载合并后的LoRA权重。部署配置比较常规设定max-model-len为8192gpu-memory-utilization开到0.9。实测下来单张4090上并发8个请求每个测试生成的延迟在8到15秒之间对于离线批量生成完全够用。部署命令大概长这样vllm serve ./lora_testgen_merged \ --served-model-name testgen-qwen \ --tensor-parallel-size 1 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --dtype float16部署好之后我把生成测试的流程接到了CI里每次代码合并触发对变更文件抽取方法请求本地推理服务生成测试代码然后自动跑一遍编译检查。编译器通过的直接提交到MR里供人工review编译不过的自动丢弃并生成提示日志。这个流程跑下来人工review的负担显著下降了。当然这里有一个合规和信任的考量AI生成的代码不能直接进生产分支必须经过真人review后才能合入CI流水线这条规则我们执行得很严格。5. 常见问题与排查思路5.1 训练效果差的排查清单经常有人跑完LoRA微调之后发现效果和基线差不多甚至更差于是怀疑LoRA没救了。我排查下来90%的问题出在数据上而不是训练参数上。你可以按这个顺序自查训练数据格式是否和chat模板匹配不匹配的话模型完全学不进去指令格式。数据里有没有坏样本尤其是那些编译不过的“半成品”模型会学坏。样本多样性够不够如果800条样本全是CRUD类型的测试模型对其他类型的方法就束手无策。验证集和训练集是否同分布我见过有人把同一条数据既放训练集又放验证集验证loss永远是0.01看起来效果炸裂上线立刻翻车。是不是训练数据太少LoRA微调虽然数据需求比全参微调少但少于300条高质量样本基本没效果。如果上面都没问题再回头检查训练超参数。我遇到过learning_rate设太大导致loss震荡、max_seq_len设太小导致代码被截断这两种情况都很典型。特别是max_seq_len如果你发现生成的测试类老是被截断后半部分全是残缺的那大概率就是这里的问题。5.2 生成结果常见的三类问题第一类问题是“测试全糊弄”生成了10个测试没有一个真正断言核心逻辑。这些测试跑起来全绿但属于典型的无效测试。解决办法是在提示词里强行规定“每个测试必须包含至少一个核心行为断言不允许仅验证返回非空”并且在筛选数据时严格把关。第二类问题是“过度Mock”模型把被测方法里的所有依赖全部Mock掉导致被测逻辑本身也没被真正执行到。典型的例子是Mock了Calculator对象然后调calculator.add(a, b)之后断言add方法被调用了——这测了个寂寞。这类问题的根源在于模型不理解哪些依赖应该Mock、哪些应该真实调用需要在数据里加入明确的“该真实调用的依赖列表”。第三类问题是“幻觉对象”模型生成了项目里不存在的类或方法。比如代码里明明用的是RedisTemplate模型却给你写了一个StringRedisTemplate。这类问题在微调之后大幅减少因为模型见过项目的真实类型了但如果项目里引入了新的依赖而训练数据没覆盖到仍会出现。遇到这种情况最快的处理方式是把新依赖的信息补充到提示词的“依赖关系”板块里。5.3 关于评估标准多说两句我在实战中发现测试生成领域最缺的不是生成算法而是一个统一可靠的评估标准。覆盖率这种指标太粗糙根本反映不出测试质量“代码能编译通过”这种指标又太初级编译通过但断言无效的测试遍地都是。推荐的做法是维护一个“缺陷注入测试集”每个被测方法都人工注入几个真实缺陷然后看生成测试能不能测出这些缺陷。这个测试集很贵但一旦建立起来就是评估模型效果最可靠的标尺比任何覆盖率数字都有公信力。6. 一些想对后来者说的话做这个项目的过程中我最深的体会是提示工程和微调不是二选一的关系而是一条流水线上的两道工序。提示工程负责把需求变成“可计算的形式”让你知道什么叫作高质量测试微调负责把“形式”固化成“肌肉记忆”让模型不假思索地按这个标准输出。两者缺一不可。另外别指望模型第一次就能生成完美的测试代码。这就像带新人最开始要手把手教规矩、给反馈、做示范等新人把项目的套路都摸透了你才能放心让他独立干活。LoRA微调本质上就是一次加速版的“带新人”。还有一个小技巧想分享一下我最后在生成测试之前都会让模型先“分析一遍被测方法的逻辑分支”输出一个分支列表然后再生成测试。这一步看起来多余但实测能把Branch Coverage再提高5到8个百分点因为模型先梳理了逻辑后面写测试的时候就不容易漏分支。这个技巧成本极低但效果显著你调完提示词之后值得一试。如果后续要继续扩展我准备把这一套流程推广到Service层的集成测试生成上并且把数据清洗部分做成一个半自动化的工具链减少人工标注的成本。到那时候再写一篇完整的实战记录和大家分享。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号