# php-tdd-service-example **Repository Path**: fastknife/php-tdd-service-example ## Basic Information - **Project Name**: php-tdd-service-example - **Description**: 用PHP进行TDD测试驱动开发的项目实战。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-07-26 - **Last Updated**: 2024-07-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # TDD测试驱动编程案例 > 分层架构下的tdd示例尝试 由于分层架构的设计,使得一个业务功能分散在不同的文件中(控制层、服务层、逻辑层、数据访问层、模型层);而网上或书本上大多数案例的业务逻辑都是写在一个文件中或同一层级中,与真实的开发有差异。这也意味着我们不能按部就班,要想办法优化流程。 控制层依赖服务层、服务层依赖逻辑层、逻辑层依赖数据访问层、数据访问层依赖模型层; 分层架构的特性,测试流程要么自下向而上、要么自下而上。自下向而上的流程缺少全局观念;自上而下缺乏依赖关系; 所以要找一个平衡点进行切入。 ## phpunit 文档 https://www.w3cschool.cn/phpunit9/phpunit9-bgxp3mdx.html - 测试命令 + 全部 `composer test` + 过滤测试 `composer test -- --filter` + 分组测试 `composer test -- --group` ## 原则 - 优先测试核心功能:确保核心功能和关键路径有充足的测试覆盖。 - 根据实际需求进行取舍:在资源有限的情况下,选择最重要和最容易出问题的部分进行测试。 > 重构以[《重构改善既有代码的设计》](https://cdn.codenews.cc/pdf/%E9%87%8D%E6%9E%84%E6%94%B9%E5%96%84%E6%97%A2%E6%9C%89%E4%BB%A3%E7%A0%81%E7%9A%84%E8%AE%BE%E8%AE%A1.pdf) 准则 ## 示例- 注册用户 ### 单元测试 - 账号密码注册 - 手机号注册 #### 账号密码注册 1. 红;构想许愿,写一个错误的测试 - 创建test/Cases/Service/UserServiceTest 类继承TestCase; 创建啊app\Service\UserService类。 - 设计UserService类设计一个动作,这个动作能够完成我们,需要的功能。同时设计这个功能的入参、出参。 - 然后在 UserServiceTest 类中调用我们设计的功能。 > 我设计UserService类设计一个动作加 register, 有两个入参 一个是account; 一个是password;(可能有人会疑惑,为什么没有confirmPassword【确认密码】,我觉得没必要,前端验证就好了) ![img.png](img.png) > 这个时候 UserService 没有register方法,结果必然会报错的。 ![img_1.png](img_1.png) 2. 绿;实现刚才设想出来的 register方法,填充它; - 单元测试,直到这个小功能的代码跑通。 ![img_3.png](img_3.png) ![img_2.png](img_2.png) 3. 重构; - 我们发现 register 里方法里做了两件事;一个是检查重复;另一个是报存到数据库;应当提炼成两个方法(参考《重构》的Extract Method); 然后细看,这两个方法都是在操作数据库;那么它们都数据属于数据访问层的代码;于是重构如下: ![img_4.png](img_4.png) - 还有就是我们的测试用例不完整,在检查用户时,代码抛出了异常,这个异常是要通过 ExceptionHaler处理正常逻辑的。所以要调整我们的测试用例: ![img_5.png](img_5.png) #### 手机号注册 > 手机号注册有两个功能,第一个是发短信,第二个才是注册 ##### 发短信 1. 红,许愿,设计发短信的出参和入参。 ![img_6.png](img_6.png) 2. 填充UserService的sendSms方法。 ![img_9.png](img_9.png) 3. 测试,使它变绿; 4. 重构。 > 上面的代码是否要重构需要看该项目采用了严格分层架构还是松散分层架构;松散分层架构,只有三行,属于简介代码,就不需重构了; 严格分层架构需要把 smsMobile::create 放到mapper里; 把smsUtil::send 放到logic里。 5. 提交git代码 ##### 手机号注册 1. 红,许愿,设计手机号注册的出参和入参。 ![img_7.png](img_7.png) 2. 绿; 填充 registerByMobile方法的内容。 ![img_8.png](img_8.png) 3. 测试通过后,我们发现之前的测试设计无法覆盖填充代码的所有分支;于是修改测试用例,使得它能覆盖所有分支。 ![img_10.png](img_10.png) ![img_11.png](img_11.png) ![img_12.png](img_12.png) 4. 重构; - 我们发现“短信”这一功能在之前的代码里也有。这就出现了一个代码的坏味道——“发散式变化”。 > 就像一个User 类中; 包含了(name,gender,avatar,mobile, address)等属性;其中address 是一个字符串类型的地址名称, 突然有一天,需要对 address 内容做补充,将省市区、邮编都放进去,于是User的类成员就变成了(name,gender,avatar,mobile, address,province,city,area,postcode);此时对于User类而言就不再符合单一原则了,因为这里有两个变化的原因,一个是用户拥有的特性,一个是地址拥有的特性。 好的办法是将地址相关信息拆成一个新的Address对象。User类包含了这个Address对象。 同理,我们要把短信相关的内容拆出来。 - 我们还发现 existsOrFail、register 这些名称在单一的注册功能(账号注册)时,命名没有问题;但加入手机号注册时,原本的命名就产生歧义了;所以这里需要修改方法命名。 - 在优化重构的时候,我们发现短信code,是为了配合测试从外部强行传入的,所以这里也要优化。 测试用例代码如下: ![img_13.png](img_13.png) ![img_14.png](img_14.png) ![img_15.png](img_15.png) 生产代码如下: ![img_16.png](img_16.png) ![img_17.png](img_17.png) ![img_18.png](img_18.png) 5. 提交git代码 ### 集成测试 完成业务层的代码编写后;再来完成控制层的代码编写; #### 账号密码注册 1. 红,编写接口控测试的代码; 创建 UserApiTest类 ![img_19.png](img_19.png) 2. 绿,实现;完善UserController 接口,是的可以通过测试。 ![img_20.png](img_20.png) 3. 重构,已经是最优解了。 ##### 手机号+短信注册 1. 红,编写接口控测试的代码;这里需要调用两个逻辑,一个是先发短信;然后用获取到的短信去注册。 ![img_21.png](img_21.png) 2. 绿,实现;完善UserController 接口,是的可以通过测试。 ![img_22.png](img_22.png) 3. 重构,已经是最优解了。