如何测试微服务对于测试工作,微服务架构给传统架构带来了更多的复杂性。一方面,随着微服务数量的增长,测试用例会不断增长;另一方面,由于微服务之间存在一定的依赖关系,在测试过程中如何处理这些依赖关系就变得异常重要。本节将从微服务架构的单元测试、集成测试和系统测试三个方面进行讨论。微服务的单元测试单元测试要求测试的范围仅限于服务,这样可以保证测试的隔离性,将测试的影响降到最低。TDD要求程序员在实际编码之前编写测试用例。当然,一开始,所有的测试用例都应该失败,然后写代码让这些测试用例一个一个通过。也就是说,编写足够多的测试用例以使测试失败,并编写足够多的代码使测试成功。这样,程序员编码的目的就会更加明确。当然,编写测试用例并不是TDD的全部。测试成功后,还需要及时重构成功的代码,消除代码的“恶臭”。一、为什么要重构代码所谓重构,简单来说就是在不改变代码外部行为的情况下,通过修改代码来完善程序的内部结构。重构的前提是代码的行为是正确的,也就是说代码功能已经过测试,测试通过了,这是重构的前提。只有正确的代码才有意义重构。那么,既然代码是正确的,为什么还要花时间改代码、重构代码呢?重构的原因是大多数程序员无法写出完美的代码。他们不能完全信任自己编写的代码,这就是为什么他们需要测试自己编写的代码,重构也是如此。总结起来,以下几个方面是软件需要重构的原因。?软件不一定从一开始就是正确的。有才华的程序员只有少数,大多数人难免会犯错,所以很多程序员都无法一次写出正确的代码,只能不断地测试和重构来完善代码。即使是像MartinFowler这样的高手也承认,他的编码水平和大多数人一样,需要测试和重构。?随着时间的推移,软件的行为变得难以理解。这种现象尤其集中在一些规模大、历史长、代码质量差的软件中。这些软件的实现要么脱离了最初的设计,要么乱七八糟让人看不懂,尤其是缺乏“活文档”的引导,这些代码最终会“烂掉”。·能运行的代码不一定是好代码。任何程序员都可以编写出计算机可以理解的代码,只有编写出人类可以轻松理解的代码的人才是优秀的程序员。正是当前软件行业存在的这些事实,使得重构成为TDD中必不可少的实践之一。程序员出于以下目的重构程序。消除重复。第一次写代码的时候,只是为了让程序通过测试。过程中可能会出现大量的重复代码和“僵尸代码”,因此需要在重构阶段剔除重复代码。使代码易于理解和修改。一开始,程序员优先考虑程序的正确性,并没有关注代码的规范,因此需要在重构阶段改进代码。改进软件的设计。好的想法不会一蹴而就。当之前的代码有更好的解决方案时,果断重构,改进软件设计。查找错误并提高质量。好的代码不仅让程序员容易理解和理解,也让程序员容易发现和修复问题。测试和重构齐头并进。提高编码效率和编码水平。重构技术有利于消除重复代码,减少冗余代码,提高程序员的编码水平。程序员编码水平的提高也会体现在编码效率上。2.什么时候应该进行重构那么,程序员应该在什么时候进行重构呢?随时重构。也就是说,把重构当做一种开发的习惯,重构应该像测试一样自然。只有三个东西,第三个是重构。当存在重复代码时,就该进行重构了。添加新功能时。增加了新的功能,调整了原有的代码结构,需要进行单元测试和重构。改正错误时。修复错误后,还需要重新进行单元测试和接口重构。代码审查。代码审查是发现“代码味道”的好时机,当然也是重构的好机会。3.代码的“臭味”如果一段代码不稳定或者存在一些潜在的问题,那么代码中往往会有一些明显的痕迹,就像食物在快要腐烂之前常常会散发出一些异味,这些痕迹就是代码“坏品味”。以下是常见的代码“难闻的气味”。DuplicatedCode(重复代码):重复是万恶之源。解决方案是提取公共功能。LongMethod(太长的函数):太长的函数会导致职责不明确、难以切割、难以理解等一系列问题。解决方案是将长函数拆分成几个函数。LargeClass(太大的类):会导致职责不清,难以理解。解决方案是拆分成几个类。LongParameterList(太长的参数列表):太长的参数列表其实并没有真正遵循面向对象的编码方式,程序员也很难理解。解决方案是将参数封装到结构或类中。DivergentChange(发散变化):当多个需求修改时,会移动这个类。解决办法是拆分代码,把经常变化的东西放在一起。ShotgunSurgery(霞弹类型修改):其实就是在没有改包的情况下改一个需求,然后会涉及到多个要修改的类。解决办法是把所有的修改点都集中起来,抽象成一个新的类。FeatureEnvy(附件复杂):一个类对其他类的依赖性太大,比如一个类大量使用了其他类的成员,这就是FeatureEnvy。解决办法是将这个类合并到依赖类中。DataClumps:数据块是经常一起出现的大堆数据。如果数据有意义,解决方案就是将结构化数据转换为对象。PrimitiveObsession(基本类型偏执狂):热衷于使用基本类型,如int、long和String。解决方案是将其修改为使用类。SwitchStatements(switch恐怖外观):当switch语句判断的条件过多时,就要考虑少用switch语句,改用多态。ParallelInheritanceHierarchies(并行继承系统):使用类继承并行连接了太多的并行类。解决方案是从继承关系中删除其中一个类。LazyClass(冗余类):对于这些冗余类,解决方法是将这些不再重要的类中的逻辑合并到相关类中,删除旧类。SpeculativeGenerality(吹嘘未来):对于这些无用的类,直接删除即可。TemporaryField(混淆临时字段):对于这些字段,解决方法是将这些临时变量集中到一个新的类中进行管理。MessageChains(过耦合消息链):使用你真正需要的函数和对象,而不是依赖消息链。MiddleMan(中间人):存在这种过度代理的问题,解决办法就是用继承代替委托。InappropriateIntimacy(亲密关系):两个类互相使用对方的私有值域。解决办法是划清界限,拆除,或合并,或改为单一链接。AlternativeClasseswithDifferentInterfaces(目的相同的类):这些类往往是相似的类,但有不同的接口。解决办法是重命名这些类,移动函数,或者抽象子类,将它们合并为一个类。IncompleteLibraryClass(不完善的库类):解决方法是再包裹一层函数或者包裹成一个新的类。DataClass(朴素数据类):这些类非常简单,往往只有公共成员变量或简单的操作函数。解决方法是封装相关操作,减少公共成员变量。RefusedBequest(拒绝遗赠):这些类表明父类中有很多方法,但只使用了有限的子类方法。解决方案是使用代理而不是继承关系。Comments(过多的注释):如果注释过多,说明代码不清晰。解决方法是先重构再写注释,去掉多余的注释,“好代码会说话”。4.减少测试依赖首先,我们必须承认,对象之间的依赖是不可避免的。功能是通过对象之间的协作来完成的,任何对象都可以使用其他对象的属性、方法和其他成员。但同时也认识到,代码中对象过于复杂的依赖关系往往是不被提倡的,因为对象之间的相关性越大,如果代码发生变化,影响范围就越大,而这并不是完全有利于系统的测试、重构和后期维护。因此,在现代软件开发和测试过程中,应尽可能减少代码之间的依赖。DI(DependencyInjection)相对于传统的JavaEE开发模式,使得代码对容器的依赖程度降低,减少了计算机程序的耦合问题。通过简单的new操作,构成程序员应用程序的POJO对象就可以在JUnit或TestNG下进行测试。即使没有Spring或其他IoC容器,也可以使用mocks来模拟对象以进行独立测试。清晰的分层和组件化代码将有助于简化单元测试。例如,在运行单元测试时,程序员可以使用stubs或mocks来代替DAO或资源库接口,从而实现对服务层对象的测试,而程序员在此过程中不需要访问持久层数据。这减少了对基础设施的依赖。在测试过程中,真实物体具有不可确定的行为,并可能产生不可预测的效果(如股票行情、天气预报)。同时,实物存在以下问题。真实的对象很难创建。真实对象的某些行为很难触发。真实对象实际上还不存在(与其他开发组或新硬件)等。正是由于上述真实对象的测试过程中存在的问题,模拟测试被广泛使用。在单元测试的上下文中,模拟对象是“模拟”具有某些“虚构占位符”功能的某些对象接口的实现的对象。在测试过程中,这些虚构的占位符对象可用于以简单的方式模拟组件的预期行为和结果,使程序员可以专注于组件本身的全面测试,而不必担心其他依赖项。模拟对象经常用于单元测试。使用mock对象进行测试是指在测试过程中,对于一些不易构造(如HttpServletRequest必须在Servlet容器中构造)或难以获取(如JDBC中的ResultSet对象)的复杂对象,使用A为测试测试方法而创建的虚拟对象(模拟对象)。mock最大的作用就是分解单元测试的耦合。如果编写的代码对另一个类或接口有依赖关系,它可以模拟这些依赖关系并验证调用的依赖关系的行为。模拟对象测试的关键步骤如下。使用一个接口来描述这个对象。在生产代码中实现此接口。在您的测试代码中实现此接口。在测试代??码中,只是通过接口引用了对象,所以并不知道引用的对象是真实对象还是mock对象。目前Java阵营主要的mock测试工具有Mockito、JMock、EasyMock等。5、mock和stub的区别mock和stub都是为了替代外部依赖对象。模拟不是存根。两者有以下区别。前者被称为mockistTDD,而后者通常被称为classicTDD。前者是基于行为的验证(BehaviorVerification),后者是基于状态的验证(StateVerification)。前者使用模拟对象,而后者使用真实对象。现在让我们通过一个例子来看看mock和stub之间的区别。如果程序员想测试发送邮件的行为,他可以像下面这样写一个简单的存根。//待测接口publicinterfaceMailservice(){publicvoidsend(Messagemsg);}/lstub测试类publicclassMailServiceStubimplementsMailServiceipvateList
