在遗留系统中工作,无论是开发新功能,修改旧功能,还是重构重振雄风,都会面临很多挑战。这些挑战主要来自丢失的业务知识、丢失的技术和烂代码。一般来说,在重构遗留系统时,首先会增加必要的测试,然后进行重构、重新设计等一系列过程,以提高其内在质量。MartinFowler在他的微服务测试策略分享中详细讨论了各种测试方法及其适用场景。在那次讨论中,他介绍了组件测试:组件是较大系统中封装良好、可独立更换的中间子系统。单独测试这些组件有很多好处。通过将测试范围限制在组件内,您可以对组件封装的行为进行验收测试,同时保持比高层测试更好的执行效率。在微服务架构中,组件就是服务本身。MartinFowler还根据测试时调用组件的方式,以及为组件构建测试替身时测试替身位于进程内还是进程外,将组件测试分为进程内和进程外两种形式。组件依赖的存储或服务。在实践中,为遗留系统添加单元测试和端到端接口测试会遇到相应的困难,但我们发现组件测试由于其以行为为中心的特性,可以在单元测试和端到端测试之间取得平衡。它为改造遗留系统提供了一个良好的起点。回避单元测试实践的被动遗留系统自最初发布以来已经过去了很多年,最初的开发人员早已离开,只为后来者留下一段代码。在遗留系统上工作通常不需要破坏其他现有功能,而只需根据需要“恰到好处”地进行修改。作为敏捷开发人员,计划的第一步是使用单元测试来确保现有功能不被破坏。但团队很快发现,遗留系统使用的技术早已失传,新团队中基本没有人懂,很难基于这样的技术构建单元测试。对于一个没有任何自动化测试的老系统,往往意味着它的内部设计是高度耦合的,想要理解清楚就已经很困难了,更不用说可测试性了。在下面的示例代码中,我们无法方便的模拟StockService依赖的WebClient实例,所以无法测试GetUpdatedStock的功能:stocks.com/stocks.json");varstocks=JsonConvert.DeserializeObject>(stockContent);returnstocks.Select(ToStock);}privatestaticStockToStock(StockResponseresp){//对象转换逻辑略}}另一方面,在旧系统上的开发工作往往意味着需要对其进行大规模的重构,以利于更好的可维护性和更容易地添加新功能。在这种背景下,即使在系统中加入了单元测试,后续的重构也会让细粒度的单元测试成为一种浪费——重构必然会修改代码设计,导致需要随之修改单元测试。与单元测试的矛盾相比,组件测试侧重于Web应用程序本身的功能和行为,而不是单个层。事实上,许多遗留系统甚至没有清晰的层次设计。组件测试是对Web应用暴露的API或网页源代码进行测试,在避免代码细节设计不当导致的被动局面的同时,也能保证Web应用行为的正确性,这就是为什么我们在legacysystem测试要保证。组件测试侧重于业务行为而不是代码实现细节。因此,它不会受到代码实现细节变化的影响。因此,组件测试不会限制重构方法的使用,也不会在调整设计时带来修改测试的额外负担。相反,它可以为重构提供强有力的保障,有助于保证重构的安全性。绕过端到端接口测试的困境在重构遗留系统的实践中,很多团队都尝试加入端到端接口自动化测试策略,以摆脱单元测试的被动局面。这样几乎可以完全忽略代码细节,直接关注业务场景。相对来说,只需要自动部署web应用和必要的依赖(如数据库)来测试应用。但在实际实施过程中,团队发现要为老系统搭建这样的环境并不容易。端到端集成测试需要在真实的Web应用实例上运行测试,要求基础设施尽可能真实,包括数据库、缓存设施等。因此,为了让端到端集成测试能够在持续集成环境中自动运行,要求应用程序及其依赖的基础设施具有自动化部署的能力。遗留系统通常自动化程度很低,无法自动部署以进行端到端集成测试。即使Web应用程序本身的部署并不复杂,它所依赖的其他服务也很难自动部署,例如SMTP服务器。在测试金字塔中,端到端测试接口测试处于较高层次。这意味着,即使自动化环境搭建成功,由于测试依赖的资源较多,测试成本也会比较高。由于端到端测试集成了系统的多个级别,因此测试用例的执行比较低级别的测试用例更脆弱、更慢。这些挑战和特点决定了我们很难在短时间内为遗留系统添加足够多的端到端接口测试用例来保证后续的重构工作。在进行组件测试时,完全不需要担心端到端测试的这些问题。组件测试通过一定的方式模拟和隔离Web应用的外部依赖,避免了部署和配置外部依赖的复杂过程。较小的专用仿真层的启动和运行速度可根据测试需求定制;如果采用进程内组件测试,可以进一步提高测试用例的运行效率和稳定性。组件测试的最佳实践是在单元测试中将Web应用本身作为被测单元,用测试替身模拟和隔离Web应用的外部依赖,根据组件中提供的API或Web页面进行测试。业务场景。行为,即组件测试。在进程内组件测试的实用方法中,我们直接在测试代码中自动构建Web应用程序所需的依赖项,启动被测服务,然后调用被测API并执行断言。下面的代码演示了这样一个测试的大致流程:动态创建一个关系数据库,启动web应用,使用web应用中repository需要的数据来准备测试,然后调用待测接口并assert结果。[事实]publicasyncvoidshould_handle_search_request_with_mocked_database(){varsqliteConnection=DatabaseUtils.CreateInMemoryDatabase(outvardatabaseOptions);varappServices=SetupApplication(sqliteConnection,outvarclient);varjim=newEmployee{Id=12,Name="Jim"};appServices.Get>>Service
