当前位置: 首页 > article >正文

TDD + DDD 双剑合璧:我是如何用测试驱动出清晰领域模型的

TDD DDD 双剑合璧我是如何用测试驱动出清晰领域模型的当业务需求像一团迷雾般模糊不清时我们往往陷入两难要么过早陷入技术实现细节导致模型偏离业务本质要么在抽象讨论中原地打转迟迟无法产出可验证的代码。三年前我在开发电商优惠券系统时正是通过TDD与DDD的协同运用找到了破解这一困局的密钥。1. 从混沌到清晰测试作为需求探针接到优惠券使用限制需求时产品文档只有一句话不同用户等级享有不同折扣力度。传统做法可能是立即设计Coupon实体和User类但TDD要求我们首先思考这个功能究竟该如何被验证我创建了第一个测试用例Test void should_reject_coupon_when_user_level_below_required() { User basicUser new User(basic); Coupon vipCoupon new Coupon().setRequiredLevel(vip); assertThrows(InvalidCouponException.class, () - vipCoupon.applyFor(basicUser)); }这个红色测试迫使我在编写实现前明确几个关键问题用户等级是简单的字符串还是需要值对象优惠券校验逻辑应该放在Coupon内部还是服务层异常类型是否需要区分不同失败场景测试即需求的特性在此显现——通过编写可执行的验证逻辑我们实际上是在用代码定义业务规则的精确表述。当测试无法轻易编写时往往意味着需求理解存在模糊地带。2. 红绿循环中的模型演进初始实现仅用20行代码就让测试变绿class Coupon { private String requiredLevel; public void applyFor(User user) { if (!user.getLevel().equals(requiredLevel)) { throw new InvalidCouponException(); } } }但重构阶段暴露出原始设计的贫血性——校验逻辑机械地比较字符串缺乏业务语义。这引导我进行以下改进将用户等级升级为值对象class UserLevel { private final int weight; public boolean canUse(CouponLevel required) { return this.weight required.getWeight(); } }引入CouponLevel领域概念enum CouponLevel { REGULAR(1), VIP(2), SVIP(3); private final int weight; // getter constructor }重构后的应用逻辑public void applyFor(User user) { if (!user.getLevel().canUse(this.requiredLevel)) { throw new InvalidCouponException(Insufficient user level); } }测试的保护网让我们能安全地进行模型深化——每次重构后运行测试确保行为不变的同时提升代码表现力。经过五轮红绿循环原本简单的字符串比较演进为具有明确业务含义的领域对象协作。3. 测试驱动出领域元素当需求扩展到限量发放时TDD自然地驱动出DDD的典型模式3.1 领域事件浮现测试用例先定义预期行为Test void should_publish_event_when_coupon_claimed() { Coupon coupon new Coupon().setTotalQuota(100); coupon.claimBy(testUser); assertTrue(coupon.domainEvents() .contains(new CouponClaimedEvent(couponId, userId))); }实现时发现需要引入领域事件机制这促使我们定义CouponClaimedEvent值对象在聚合根中添加领域事件收集机制设计轻量级的事件发布接口3.2 聚合根的明确多次测试迭代后我们意识到Coupon需要维护已发放数量的不变性发放操作必须保证事务一致性使用记录需要关联到具体用户这些认知最终固化为代码中的聚合边界class Coupon { private CouponId id; private int totalQuota; private int claimedCount; private ListUsageRecord usages; public void claimBy(User user) { if (claimedCount totalQuota) { throw new CouponExhaustedException(); } this.claimedCount; this.usages.add(new UsageRecord(user.id())); this.registerEvent(new CouponClaimedEvent(id, user.id())); } }4. 双循环开发模式经过多个需求迭代我总结出以下实践模式4.1 外层DDD循环阶段活动产出物业务探索事件风暴/用例分析限界上下文划分模型设计聚合/实体/值对象识别领域模型图实现规划确定技术实现路径模块划分/接口设计4.2 内层TDD循环业务规则测试从领域专家角度编写验收测试单元测试针对具体领域对象编写细粒度测试实现用最简单代码通过测试重构提升模型表达力保持测试通过这种双循环模式确保我们既不会过早陷入技术细节通过DDD保持业务视角也不会构建出无法验证的抽象模型通过TDD保证可执行性。5. 实战避坑指南在金融级优惠券系统开发中我们遇到几个典型问题陷阱1测试过度依赖实现细节// 反模式测试耦合内部状态 Test void bad_test_example() { coupon.claim(); assertEquals(1, coupon.getInternalCounter()); // 脆弱测试 } // 改进测试业务可见行为 Test void good_test_example() { coupon.claim(); assertFalse(coupon.isAvailable()); // 基于业务语义 }陷阱2领域服务膨胀当发现CouponService超过300行时我们通过以下手段优化将校验逻辑下移到值对象用领域事件替代过程式调用引入策略模式处理差异化规则陷阱3测试数据构建困难通过构建测试专用工厂方法解决class CouponTestBuilder { private CouponLevel level CouponLevel.REGULAR; private int quota 10; public static CouponTestBuilder newCoupon() { return new CouponTestBuilder(); } public Coupon build() { return new Coupon(level, quota); } } // 使用示例 Coupon coupon CouponTestBuilder.newCoupon() .withLevel(VIP) .withQuota(100) .build();6. 效能提升技巧技巧1测试命名即文档// 糟糕的命名 Test void testCase1() {} // 良好的命名 Test void should_apply_20percent_discount_when_user_is_vip_and_cart_over_1000() {}技巧2自定义断言提升可读性public class CouponAssert { public static void assertValidFor(Coupon coupon, User user) { if (!coupon.isApplicableFor(user)) { fail(Coupon should be valid for user.getLevel()); } } } // 使用示例 Test void check_eligibility() { CouponAssert.assertValidFor(vipCoupon, vipUser); }技巧3可视化测试报告通过Allure等工具生成包含以下信息的报告业务用例覆盖情况领域模型元素测试覆盖率业务规则验证矩阵在持续交付流水线中这些报告成为领域模型健康度的重要指标。当新增需求导致测试覆盖率下降时团队会立即收到警报并补充验证场景。

相关文章:

TDD + DDD 双剑合璧:我是如何用测试驱动出清晰领域模型的

TDD DDD 双剑合璧:我是如何用测试驱动出清晰领域模型的 当业务需求像一团迷雾般模糊不清时,我们往往陷入两难:要么过早陷入技术实现细节,导致模型偏离业务本质;要么在抽象讨论中原地打转,迟迟无法产出可验…...

5.3小记1

现在已经爬取了猫途鹰上九寨沟风景区和澳门所有评论数量大于两百的景点的评论了,筛选条件是总评论数量大于两百,爬取数据是中文简体内容,所以数量实际并不多。而且九寨沟景区的景点并不仅仅有九寨沟风景区,这只是一个总的&#xf…...

[特殊字符]️ 从零到一:手把手教你用 re.findall() 打造智能爬虫(2026最新实战)

目录 一、前言:为什么 2026 年我还要写一篇关于 re.findall() 的爬虫文章? 二、 re.findall() 到底是什么? 2.1 一句话解释 2.2 re.findall() 三大返回值陷阱(90% 的新手都踩过) 陷阱一:有分组时,返回元组列表 陷阱二:嵌套分组,只捕获最内层 陷阱三:(?...) 非…...

DLSS Swapper终极指南:3步完成游戏性能优化,告别手动替换烦恼

DLSS Swapper终极指南:3步完成游戏性能优化,告别手动替换烦恼 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 你是否曾在《赛博朋克2077》中为了提升几帧而翻遍游戏目录?是否在《控制…...

【RT-DETR涨点改进】TGRS 2026 |独家创新首发、下采样涨点改进篇| 引入MWHL最大池化-小波下采样,同时融合最大池化与小波变换的优势,助力红外小目标检测,遥感目标检测有效涨点

一、本文介绍 🔥本文给大家介绍使用 MWHL最大池化-小波下采样 改进RT-DETR网络模型,在下采样阶段同时融合最大池化与小波变换的优势,在保留强响应语义信息的同时有效维持特征的结构细节,从而缓解传统下采样过程中小目标特征易丢失的问题。该模块通过频域与空间域信息的协…...

多核处理器内存分区技术解析与工程实践

1. 多核处理器系统分区的核心挑战与设计哲学在嵌入式系统领域,我们正面临一个有趣的矛盾:一方面,现代设备的功能复杂度呈指数级增长,导致单个子系统规模不断扩大;另一方面,这些子系统又需要更紧密的集成协作…...

通过Python快速编写第一个调用Taotoken多模型聊天补全的程序

通过Python快速编写第一个调用Taotoken多模型聊天补全的程序 1. 准备工作 在开始编写Python程序调用Taotoken多模型聊天补全之前,需要确保已经完成以下准备工作。首先需要注册Taotoken账号并获取API Key。登录Taotoken控制台后,可以在API Key管理页面创…...

【RT-DETR涨点改进】TGRS 2026 |独家创新首发、特征融合改进篇| 引入HEWL小波特征融合模块,通道-空间-频域交互联合高频增强,助力红外小目标检测,多模态目标检测有效涨点

一、本文介绍 🔥本文给大家介绍使用 HEWL小波特征融合模块 改进RT-DETR网络模型,作用在于在特征融合与上采样阶段引入频域高频信息指导特征重建,使模型在检测过程中不仅关注语义信息,还能够有效保留目标的边缘与细节结构,从而提升对小目标和弱目标的表达能力。通过小波分…...

Cursor规则集:用AI代码助手实现团队编码规范自动化

1. 项目概述:当你的代码编辑器开始“思考”如果你是一名开发者,最近可能频繁听到一个词:Cursor。它不再仅仅是一个光标,而是一款正在悄然改变许多程序员工作流的AI代码编辑器。而今天要聊的,不是Cursor本身&#xff0c…...

基于强化学习的层次化知识检索系统设计与优化

1. 项目背景与核心挑战 大型语言模型(LLM)在知识检索任务中常面临层次化数据结构处理的困境。传统检索方式依赖静态索引和固定匹配模式,难以适应知识图谱、文档树等嵌套层级场景。我在实际项目中发现,当用户查询涉及"计算机科…...

XIAO双通道Wi-Fi电能表:家庭能源监控利器

1. 项目概述:XIAO双通道Wi-Fi交流电能表最近在折腾家庭能源监控系统时,发现Seeed Studio新推出的XIAO双通道Wi-Fi交流电能表是个很有意思的设备。这款基于ESP32-C6模组的电能表配备了两个100A电流互感器(CT钳),可以直接接入Home Assistant实现…...

稀疏计算优化LLM预训练:原理、技术与硬件加速

1. 稀疏计算在LLM预训练中的核心价值大型语言模型(LLM)预训练的计算瓶颈主要来自矩阵乘法运算。在Transformer架构中,前馈网络(FFN)模块占据了大部分计算量——随着模型规模增大,FFN的计算占比可高达总预训练浮点运算的50%以上。传统密集矩阵乘法需要处理…...

Agent 一接骨架屏页面就开始误判完成态:从 Readiness Signal 到 DOM Stabilization 的工程实战

浏览器 Agent 一进企业后台,最容易踩的坑往往不是页面太慢,而是页面看起来已经“加载好了”,实际仍停留在骨架屏、占位卡片和半成品 DOM。⚠️ 人类会等列表真实出现再点,Agent 如果只看到按钮可见、节点已挂载,就可能…...

A11y Bridge:为AI Agent实现毫秒级Android自动化交互

1. 项目概述:为AI Agent赋予“实时视觉”与“触手”在移动应用自动化测试和AI智能体交互领域,我们常常面临一个核心瓶颈:如何让运行在电脑上的程序“看见”并“操作”手机屏幕?传统的方法,无论是基于adb shell screenc…...

Flutter 跨平台实战:OpenHarmony 健康管理应用 Day3|页面路由跳转与多表单联动实现

🎯 Flutter 跨平台实战:OpenHarmony 健康管理应用 Day3|页面路由跳转与多表单联动实现 欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net 🚀 前言 大家好!本篇是我真实完成 Flutte…...

Agent 一接富文本编辑器就开始改错块:从 Selection Grounding 到 Undo Fence 的工程实战

很多团队以为 Agent 接上富文本编辑器后,剩下只是“把字打进去”。真到生产环境,事故常出在另一层:模型知道要改哪一句,人却只看到整篇文档被误覆盖。最常见的现象不是不会写,而是改错块、删错段、撤销失控。这类问题一…...

Agent 一接浏览器本地存储就开始串租户:从 Storage Namespace 到 Session Snapshot 的工程实战

很多浏览器 Agent 在演示环境里很稳,一进多租户后台就开始出现“登的是 B 账号,提交的却是 A 组织草稿”的事故。⚠️ 问题不在 DOM 识别,而在浏览器本地状态没跟任务边界一起清空。Cookie 已刷新,页面却仍会从旧的 localStorage、…...

2026年企业网站建设趋势:为什么说“移动优先”不再是可选项而是必选项?

2026年企业网站建设趋势:为什么说“移动优先”不再是可选项而是必选项?如果你今天还在问“我的网站需要适配手机吗”,你可能已经错过了两年的窗口期。2026年,超过65%的网站流量来自移动设备。谷歌早已全面采用移动优先索引——这意…...

如何在Windows上快速安装安卓应用:APK Installer完整免费指南

如何在Windows上快速安装安卓应用:APK Installer完整免费指南 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 想在Windows电脑上轻松运行安卓应用&#xff…...

5个理由告诉你为什么LyricsX是macOS上最智能的歌词伴侣

5个理由告诉你为什么LyricsX是macOS上最智能的歌词伴侣 【免费下载链接】LyricsX 🎶 Ultimate lyrics app for macOS. 项目地址: https://gitcode.com/gh_mirrors/ly/LyricsX 还在为找不到合适的歌词软件而烦恼吗?LyricsX作为一款专为macOS设计的…...

git worktree

文章目录1.简介2.格式3.常用选项4.示例4.1 为现有分支创建 worktree4.2 创建新分支并同时创建 worktree4.3 查看所有 worktree4.4 移除不再需要的 worktree4.5 锁定 worktree(防止在 prune 时被清除)4.6 清理已删除的 worktree 引用5.小结参考文献1.简介…...

做端侧 AI 应用,绝不等于强行跑大模型

【端侧 AI 实践】如何在 20MB 包体积限制下,实现小程序的"实时"AR 视觉与 VLM 场景理解? 在构建基于 AI 的摄影辅助小程序时,我们面临了一个非常经典且棘手的端侧 AI 架构矛盾。 理想情况下,我们希望通过视觉大模型&…...

终极指南:如何快速下载GitHub中的单个文件和目录?

终极指南:如何快速下载GitHub中的单个文件和目录? 【免费下载链接】DownGit Create GitHub Resource Download Link 项目地址: https://gitcode.com/gh_mirrors/do/DownGit 你是否经常遇到这样的情况?在GitHub上发现了一个很棒的开源项…...

解决ArduinoIDE2.2.X以上版本不能使用ESP8266-littlefs问题

参考该篇方案 GitHub - earlephilhower/arduino-littlefs-upload: Build and uploads LittleFS filesystems for the Arduino-Pico RP2040, RP2350, ESP8266, and ESP32 cores under Arduino IDE 2.2.1 or higher GitHub #videoTogetherLoading {touch-action: none;height: …...

OpenClaw授权防火墙:从原理到实践,构建Web3代币授权主动防御体系

1. 项目概述与核心价值最近在开源社区里,一个名为openclawunboxed/openclaw-approval-firewall的项目引起了我的注意。乍一看这个标题,它融合了“OpenClaw”、“Approval”和“Firewall”三个关键词,对于熟悉区块链和智能合约安全领域的朋友来…...

GraphRAG与Dify集成实战:构建基于知识图谱的智能问答应用

1. 项目概述:当知识图谱遇上智能体,GraphRAG与Dify的化学反应最近在折腾一个挺有意思的开源项目,叫brightwang/graphrag-dify。如果你同时关注知识图谱(GraphRAG)和AI应用开发平台(Dify)这两个领…...

Cursor Free VIP终极指南:如何永久免费使用AI编程助手的完整教程

Cursor Free VIP终极指南:如何永久免费使用AI编程助手的完整教程 【免费下载链接】cursor-free-vip [Support 0.45](Multi Language 多语言)自动注册 Cursor Ai ,自动重置机器ID , 免费升级使用Pro 功能: Youve reache…...

Claude Code 浏览器自动化插件 Browserbase Skills 完整上手指南。

最近有个问题一直在我脑子里转:AI 编程助手写代码够厉害,但到了"打开网页、点按钮、填表单"这种活,它反而束手无策。Claude Code 能帮你生成一整个项目,却不能帮你自动登进去测一下效果。多多少少感觉是有点讽刺。直到我…...

linux反代

一ddnsgo泛域名解析二...

分类树方法(CTM)在软件测试中的高效应用

1. 分类树方法(CTM)在软件测试中的核心价值在嵌入式系统和安全关键软件的测试实践中,我们常常面临一个根本性矛盾:如何用有限的测试资源覆盖近乎无限的输入组合?传统的手工测试设计往往依赖工程师的经验直觉&#xff0…...