使用 PHP、PHPUnit 和 Symfony 框架进行测试驱动开发——调度系统的实用案例研究





5.00/5 (4投票s)
以测试驱动开发指南为导向,辅以 PHP、PHPUnit 和 Symfony 框架技术,原型化一个用于当地医疗诊所患者调度管理的 Web 系统
1. 引言
当一个组织处理大量冗余、过时和琐碎(ROT)数据时,该组织的生产力和总体产出都会受到打击。如果制定了适当的计划和解决方案,ROT 数据的影响可以很容易地解决。
根据 Veritas 全球数据冰山报告,33% 的数据是 ROT 数据,但许多组织会长期保留这些数据。当然,对删除键的恐惧是可以理解的,因为意外删除有价值的数据可能会导致严重的财务和法律后果。然而,保留冗余、过时或琐碎的数据也不是一个琐碎的问题(Robinson,2023)。
许多患者调度系统由于医生新日程表的开放过程而导致工程结构高度复杂。这些系统需要处理每个日程表的开始和结束时间段,并在数据库中创建一个带有唯一小时的新行。这个过程需要很长时间才能在医疗诊所的某个时间段内(通常,这个过程每月都会运行一次)为每位医生创建所有预定的小时。只有在完成这个过程之后,我们才能执行患者在诊所与医生进行预约的调度。在这个系统中,我们观察到许多小时从未被安排给患者,这可以被称为创建了 ROT(冗余、过时和琐碎)数据,正如 Gaidargi(2022)所观察到的。
上述过程是一种浪费,因为每种医疗专业的咨询时间可能不同,这增加了复杂性,可能影响每个日程表的创建,需要更多的时间和存储空间。
本项目旨在利用计算系统,根据ROT指南,为经典的临床调度系统创建解决方案,以避免或最大限度地减少问题。本提案旨在定义一个案例研究环境,以实际方式验证在整个开发过程中应用测试的软件开发方法。
2. 目标与方法
Web 系统已变得流行,能够实现越来越精细、规则更复杂、更难以测试的新业务。
本文描述了如何实现一个原型,用于在拥有多位医生的诊所中创建日程表。该原型仅根据日程表配置(包含日期和时间)和所有已预约的日程表,显示患者可以预约咨询的可用时间。这足以计算出用户可以为医生安排新患者的所有空闲时间。
通过 TDD(测试驱动开发),我将确保新的实现、更改或重构不会破坏系统不同部分的任何规则。并且随着时间的推移,所有验证都将再次执行,所有角色都将重新测试,从而保证高质量。
3. 开发
本项目的案例研究是基于对真实应用环境的分析而构建的,该环境将作为本项目的研究实验室,提供需求、问题和实际验证测试空间的信息,从而提供一个非常接近现实的上下文,进一步丰富所获得的结果。协商、上下文问题和功能列表是基于经验观察技术、会议、技术访谈、问卷调查和沉浸式会议。
3.1. 功能规范
原型的操作包括允许医疗专业人员或接待员登记患者、登记医生、为医生开放预约簿、为患者安排医生预约以及取消预约的功能。所有开发都将通过应用 TDD(测试驱动开发)方法完成,其中测试将在代码之前完成,考虑到软件必须执行正确调度的不同情况。
因此,在对领域进行实际分析以理解领域、理解问题并确定系统的基本功能需求之后,我们进行了一个最小化的项目来启动工作。从功能上讲,系统将基于一个极简模式来启动开发活动,以允许在高度演进和易受变化影响的波动环境中进行真正的TDD应用实验室,这些变化在开发过程中仍将有目的地被阐明。
系统的结构将基于三个明确划分的模块,如下面的图1和图2所示
- 行政维护模块,
- 维护服务模块和
- 日程管理模块。
为了这三个主要模块的运行,系统将允许两种用户,也定义明确
- 行政用户 - 由诊所本身代表;以及
- 诊所工作人员 - 柜台类型的工作人员
下面的用例图(图1和图2)演示了系统参与者如何与系统交互以执行此原型中定义的操作。图1展示了系统ADM维护模块可用的操作。
根据图表(图1),可以识别出系统对员工角色(角色)的主要功能。其中包括
- 客户的 CRUD
- 诊所的 CRUD
- 员工的 CRUD
图2展示了系统服务维护和管理模块可用的操作。
根据图表(图1),可以识别出系统对诊所用户角色(persona)的主要功能。其中包括
来自服务维护模块
- 专家医生的 CRUD
- 临床患者的 CRUD
- 日历的 CRUD
来自预约管理模块
- 安排预约
- 预约服务管理
业务实体是相关实体,它们共同定义了一个预约的故事:CLINICA
、MEDICO
、AGENDA_DATA
、CLIENTE
和PACIENTE
。管理实体集中在AGENDA
类型的实体周围:AGENDA
、AGENDA_CONFIG
、AGENDA_DATA
和AGENDA_STATUS
。USER
实体负责管理系统操作用户。
图3显示了为项目设计的模型。此图是类图,演示了将使用的数据模型和实体,以及系统之间的关系。
根据所呈现的分析和建模(图3),我们有用户控制、业务和关键流程实体
1. 用户控制数据
USER
- 系统用户。他们是具有操作权限(行政协助类型)的诊所员工。
2. 业务模型数据
CLINICA
- 该实体允许系统泛化,使得单个安装可以允许多个控制,例如,就像每个诊所都是一个配置文件,甚至是同一临床操作网络的多个分支。MEDICO
- 这是系统中的一个被动实体(当前版本)。这是将成为定义服务关键的医生。每个专家都会将可用性传递给负责配置每位医生日程并在系统中操作的系统操作员(用户)。CLIENTE
- 这是诊所的客户。它是启动调度过程的强制实体。客户可以为自己安排预约,在这种情况下,他将是患者本人,也可以为其他患者安排预约。PACIENTE
- 是护理对象。虽然意识是安排的基础,但患者是护理的强制基础。
3. 关键流程模型数据
AGENDA
- 它是系统中议程的定义(结构)。基本上,它是日程表的逻辑元模型,以及它应如何在系统中处理。AGENDA_CONFIG
- 与议程结构关联时,定义专家日历的信息。此实体的一个强制项是与专业人员的关系。AGENDA_DATA
- 它是集中笔记的实体。每次预约都需要有一个议程、一个专业人员和一个患者(至少他们是否同时是客户)。AGENDA_STA_STATUS
- 此实体使管理agenda_data
状态更加灵活。管理过程在此实体中注册,并同时由状态机过程(设计模式)控制。
3.2. 技术规范
为了复制具有多个服务器的Web环境,将使用 Docker
(https://www.docker.com/),其中包含三个容器来托管应用程序,一个包含 Nginx
(https://www.nginx.com/)的容器,一个包含 MariaDb
(https://mariadb.org.cn/)的容器,以及一个用于运行 PHP
(https://php.ac.cn/)应用程序的容器,如图4所示。
开发环境使用 Docker 模拟一个分布式服务器环境,包括一个 Nginx
(2023) 服务器(一个处理 HTTP 协议、接收请求并执行 PHP 应用程序的服务器)、一个 PHP 服务器和一个 MariaDb
(2023) 数据库服务器。
PHPUnit
(PHP, 2023) 允许执行在原型实现过程中开发的单元测试。使用 TDD 的开发周期包括在实现之前编写测试,这创建了一个开发流程,即我们创建测试,它将在我们实现之前失败,并在部署之后通过,但每次我们执行这个循环时,我们都会运行所有测试,并确保新的部署不会破坏已经创建的内容。
Symfony
(2023)为原型构建提供了基础,支持现代Web系统中使用的概念,我将使用允许用户通过HTTP协议(使用浏览器)访问不同URL的路由,Twig HTML模板,ORM(对象关系映射器)Doctrine,它提供了一种简单的方式来使用实体类定义数据库表及其之间的关系,从定义实体类(或来自MVC - 模型视图控制器)创建的迁移以及路由上的系统权限,以确保用户拥有访问给定页面的正确凭据。
此图(图4)展示了主要技术及其模块和依赖关系。在此图中,还可以识别出一个部分独立的上下文被设置为进行测试,主要是构成TDD过程演进输入的单元测试。
需要指出的是,尽管有所规划,但在本版本中,没有必要为测试准备专门的数据库实例;项目中相关的测试是典型的功能定义和业务规则的单元测试。在这项研究中,技术性更强的测试被忽略了,没有作为优先级进行单元执行。
4. 结果
所获得的系统可以通过Github存储库的存储库配置文件直接访问,该配置文件是公开可用的(Cardenas,2023.a)。通过这个系统,由于基于容器的部署机制,可以非常简单地组装其实现和执行(Cardenas,2023.b)。
同样,单元测试主体也是公开分发的,并且可以根据与测试基准相对应的包在同一项目中轻松识别。
通过在 Docker-PHP 项目(Cardenas,2023.b)中使用 Docker-compose,设置一个本地安全的环境变得简单便捷,只需配置 Symfony 框架与数据库连接信息即可。一旦配置完成,只需访问 PHP 容器执行迁移命令,即可自动创建数据库表并创建一个 ADMIN 用户,之后即可使用该应用程序。
存储库中提供的原型和分布式部署(Cardenas,2023.a)有一个 ADMIN 用户,该用户管理客户及其诊所。它还为客户注册用户,该用户负责管理医生、患者、日程表以及为患者安排医生。
日程安排是根据开始和结束日期、开始和结束时间以及每次咨询的持续时间配置的。这允许为同一时间段创建多个日程安排,但不同日程安排之间不能有重叠时间。
为了确保预约中没有 ROT 数据,只有在进行新的预约时,才会为某一天、某个时间段和某个患者在数据库中生成新行。计算某一天可用预约时间的算法必须接收所选日期和日程安排的所有已预约时间,并返回可用的时间供进行预约的用户选择。
从本节开始,将从功能角度展示原型示例。图5显示了系统主屏幕的一种查看可能性。可以看到系统的主用户屏幕(用户),即诊所员工。该用户可以管理医生、患者和预约。正是由于这种权限,即对日程表的控制,该用户被认为是系统的主要操作用户。
同样在这张图片(图5)中,我们可以看到系统中的专家医生列表,举例说明了管理预约的其中一种方式。用户可以从这个专家专业人员列表中进行:删除、编辑或控制他们的预约日程。
图6演示了在系统中注册新专业人员的过程。此创建过程中的一个不同点是,从调色板中定义一种颜色,该颜色将用于显示此专业人员的日程。
这种填写方式会给系统用户在医生注册过程中带来困难,但其目的是在诊所总日程中呈现每个专业人员时,视觉上便于识别。
由于医生在系统中注册的次数远少于日历显示次数,因此在填写新专家时使用此组件很容易被证明是合理的。甚至有可能在系统的下一版本中默认定义尚未使用的颜色,作为预设。
作为议程和预约操作的示例,图 7、8 和 9 展示了诊所用户(员工)在系统中的演示流程。在这些示例中,用户操作了系统的两个主要功能
- 创建并配置医生日程(图6),这指的是专科医生的可用性;
- 查看日历(图7);
- 设置新患者请求的预约(图8),最后
- 获取综合诊所的咨询汇总信息(图9)。
所有这些都由员工完成,他在诊所患者和系统之间扮演互动角色,而公共和在线访问模块在原型应用中尚不可用。
此图(图7)展示了ADMIN用户操作栏的独家操作。从这些示例中,还可以看出对系统可用性的关注,既考虑了标准化和整洁的美学,也考虑了用户操作的便捷性。这些特性得益于项目中使用的PHP框架直接提供的资源,这尤其加快了系统演示层(前端)中界面组件的开发。
在图8中,可以识别出诊所日历的操作。该日历以颜色区分每个医生,清晰地展示了诊所每个专科医生的日程。在左侧,该屏幕允许仅由专业人员操作,紧接着下方是选择诊所患者,以便进一步细化搜索过滤器。
在日历正上方的顶部栏中,默认情况下,可以看到日历的导航栏,它按月显示。
图9展示了原型调度操作的另一个步骤。该图显示了已调度预约的摘要和用于添加新活动的模态框。在此步骤中,员工类型的用户选择专业人员的日程,并填写预订服务(预约)所需的信息,即技术上定义了系统三个基本实体之间的关系:一位专科医生、患者和一个时间(日期和时间)。
排班时间(AGENDA_DATA
)通过模态屏幕定义,以方便员工操作系统。其中一个方便之处在于预填充数据和默认数据,即专业人员和日期信息。
此功能(定义预约)包含系统的主要业务规则。该功能模块集中了为系统构建的大部分单元测试。由于这项工作的主要目标是分析 TDD 对原型开发的影响,因此“安排预约”是主要观察和研究验证的重点。
此屏幕(图10)还体现了与系统相比,在GUI(图形用户界面)界面组件方面极大的困难;然而,同样地,它得到了框架资源的支持,这些资源有助于构建这种类型的模块,从而允许根据TDD在系统构建过程中的演进所产生的理解来阐明、改进甚至纠正应用于表示层的业务规则。
在完成了构成处理预约过程的这些基本步骤之后,系统允许对已服务的信息进行压缩视图,总体上是整合的,但同时探索了一些易用性定义,促进了易用性;例如在颜色使用、分组、表格和带有日常流程的导航栏方面的持久性。
为了方便此页面的操作,系统允许对此报告页面进行视觉重新配置。用户在必要时可以通过修改搜索过滤器、患者或医生来重新呈现数据。
对于原型的当前版本,系统仍然不允许按时间段修改此报告(图10)的视图。也就是说,当前系统不允许按月或按周显示笔记,例如。
5. 验证过程
TDD(2023)被应用于确保在计算日程和日期的可用时间算法中,不同情况下都能表现出正确的行为:“即使专科医生一天有多个日程安排,每个咨询的持续时间配置也不同”。
测试涵盖系统的两个主要方面
- 医生日程创建的验证(保证两个日程的日程表不能重叠的规则)和
- 计算日程中某日期可用时间的服���(接收现有日程和预约以计算空闲时间的方法),用于在用户选择的某日期显示日程中的可用时间。
由于测试确保了主要流程和一些其他流程始终正常运行(图11),因此可以专注于以前未探索的其他流程,在测试时,我发现这些流程的行为与系统的操作不符。其中一个问题是为同一时段创建两个日程,在第一个版本中是不允许的,但在进行手动测试时发现了这种需求,考虑到时间而不是仅仅日期,现在允许为医生创建多个日程,只要没有冲突的日程(两个日程中有相同的可用时间)。
由于在手动测试过程中发现了意外行为,因此实施了单元测试以确保此规则随着时间的推移保持不变,即使通过重构和代码更改也是如此。
3.5. 小步快跑验证
“小步快跑”(baby steps)是 TDD 过程的重要组成部分,它提供了一个安全的环境来推翻和思考业务规则。在开发验证议程创建的规则时,第一步是确保医生不能为两个议程创建相同的日期。
在流程的下一个时刻,它开始在这个验证中插入小时,结果是我的验证代码崩溃了,这时,我没有看到根本原因,我花费了数小时来理解为什么我的单元测试在综合测试中崩溃了。
被破坏的测试是关于两个具有相同开始和结束日期的议程的,这个测试需要被移除,因为在解决方案中添加小时后,可以存在两个具有相同日期范围的议程,但是小时不能有交集,我创建的下一个测试都有所有小时验证,换句话说,当我开始使用小时工作时,规则发生了变化。
第一次按小时测试开始了一个具有更复杂场景的测试套件,如下面的代码1所示,每次测试都得到了保证。
代码1. 一个“小步快跑”测试函数 testPrimeiraAgendaComHorario
// TestFirstAgendaWithHour
public function testPrimeiraAgendaComHorario()
{
$agenda = new Agenda();
$agenda->setDataInicioAtendimento(new DateTime('2018-10-01'));
$agenda->setDataFimAtendimento(new DateTime('2018-10-30'));
$agenda->setHorarioInicioAtendimento(new DateTime('09:00'));
$agenda->setHorarioFimAtendimento(new DateTime('12:00'));
$resultado = $this->agendaValidator->validaDataDisponivel($agenda,[]);
$this->assertTrue($resultado);
return [$agenda];
}
构建了额外的场景以需要更复杂的实现,并且对于我只用一个日期编写的所有测试用例,我都进行了复制并添加了小时的可能性。
完成日程创建的所有测试用例后,我的测试套件有18个测试,仅用于使用日期和时间验证日程创建规则,以确保医生在没有小时交叉的情况下,在一段时间内可以有多个日程。
结论
取得的成果是使用Symfony框架的应用程序,它提供了一种快速创建数据库表的方式,通过在原型实现过程中创建的迁移模式,以及可以在开发周期中执行的测试,以确保本工作过程中已经创建的功能不会发生规则更改,这些测试可以并且应该在自动化环境中,在每次部署之前执行。
从分析的角度来看,实际开发在开始时会比较缓慢,但由于确保了新的实现或代码重构能为每个开发周期带来更高的质量,因为它不会破坏现有测试。如果发生这种情况,开发人员可以与其他利益相关者确认更改是否确实应该修改已实现的规则。考虑到基于TDD跟踪的实现过程,一个真正的优势是修改、重新考虑或在开发期间仍需考虑的新插入的安全保障。
源代码
整个项目在 GitHub 上的公共存储库中可用。项目的结构,如图 12 所示,是根据用于加速系统开发、组装和部署的标准框架定义的。
该项目始终以良好的开发实践和设计标准为指导。所有业务实体都遵循面向对象基础的指南和实践,同时还与数据模型制定阶段中预定义的数据模型保持良好且直接的关系。
根据可用的项目,项目约束集中在项目路径medicalScheduleProject/src/Validator/Constraints/中。这种组织尤其促进了对系统业务规则的理解的集中和验证,这些规则在整个项目中的TDD周期迭代中不断演变。
附在存储库中可用的项目,构建在TDD中的测试基准也可用且定义良好:“medicalScheduleProject/tests/”,如图13所示。
基本上,核心或单元测试分组在以下包中:Entity
、Repository
、Service
和Validator
,这些包基本定义了单元测试的性质和重点。
这两个项目都在 GitHub 上公开,包括完整的源代码和单元测试套件,都在同一存储库中(Cardenas,2023.a),访问链接如下。
为了便于整个项目的复制,还提供了(Cardenas,2023.b)作为一个结果和项目贡献,完整的容器已准备好,以便于初始化,如下所示
- 从 https://github.com/williamCardenas/medicalScheduleProject 下载源代码
- 从 https://github.com/williamCardenas/docker-php 下载源代码
参考文献
- Aniche, M. F., Bavota, G., Trude, C. Gerosa, M. A., Deusen, A. (2018). Code smells for Model-View-Controller architectures. [在线]. 可用地址 https://pure.tudelft.nl/portal/en/publications/code-smells-for-modelviewcontroller-architectures(55788fc3-56ed-4756-8667-cbf3f1e885db).html。访问日期:2022年11月。
- Aniche, Maurício., Gerosa, Marco Aurélio.(2015). Does test-driven development improve class design? A qualitative study on developers’ perceptions [在线]. 可用地址 https://journal-bcs.springeropen.com/articles/10.1186/s13173-015-0034-z。访问日期:2022年11月。
- Cardenas, William C.(2023.a). Medical Schedule Project [在线]. 可访问 https://github.com/williamCardenas/medicalScheduleProject。访问时间:2022年11月。
- Cardenas, William C.(2023.b). docker-php [在线]. 可访问 https://github.com/williamCardenas/docker-php。访问时间:2023年5月。
- Cardoso, A., Aniche, M. (2022) .Test-Driven Development: Teste e design no mundo real com PHP. São Paulo: Editora Casa do Código. 访问日期:2022年10月。
- DBever (2023) DBeaver Community Free Universal Database Tool [在线] 可访问 https://dbeaver.io/。访问日期:2023年5月。Deusen, Arie. V., Aniche, M., Aué, J., Slag, R., Jong,
- M., Nederlof, A., Bouwers, E. (2017). A Collaborative Approach to Teaching Software Architecture. [在线]. 可访问 https://pure.tudelft.nl/portal/en/publications/a-collaborative-approach-to-teaching-software-architecture(0c7f2aeb-f2d6-4c56-9ab7-5f47f73d133f).html。访问日期:2022年11月。
- Docker (2023) Docker 官方网页. [在线] 可访问 https://www.docker.com/,访问时间:2023年4月。
- Gaidargi, Juliana.(2022). 如何减少数据浪费 [在线]. 可访问 https://www.infonova.com.br/gestao-de-ti/desperdicio-armazenamento-dados。访问时间:2022年3月。
- MariaDb (2023) MariaDB 首次在线披露门户网站 [在线] 可访问 https://mariadb.org.cn/,访问日期:2023年4月。
- Neves, Glauco Silva.(2014). 一种基于 TDD 和重构的软件产品线构建的响应式方法 [在线]. 可访问 https://repositorio.ufsc.br/bitstream/handle/123456789/129071/332126.pdf?sequence=1&isAllowed=y。访问日期:2022年12月。
- Nginx (2023) Nginx 项目网站 [在线] 可访问 https://www.nginx.com/,访问时间:2023年4月。
- Potencier, F. (2022) Symfony 6: The Fast Track 官方网页。Symfony SAS. [在线] 访问日期:2022年11月。
- PHP (2023) PHP 主页 [在线] 可访问 https://php.ac.cn/,访问日期:2023年4月。
- Robinson, P. (2023) 什么是 ROT(冗余、过时和琐碎)数据以及如何管理它 [在线] 可访问 https://www.lepide.com/blog/what-is-rot-data-and-how-to-manage-it/,更新日期 - 2021年5月26日。访问日期:2023年5月。
历史
- 2023年5月21日 - 文档创建
- 2023年5月23日 - 文本修订
- 2023年5月26日 - 首次修订和验证
- 2023年5月27日 - 改进图片展示
- 2023年5月28日 - 包括验证过程详情
- 2023年5月29日 - 文档提交