如何写出具有良好可测试性的代码?yuanrw

单元测试在一个完整的软件开发流程中是必不可少的、非常重要的一个环节。通常写单元测试并不难,但有的时候,有的代码和功能难以测试,导致写起测试来困难重重。因此,写出良好的可测试的(testable)代码是非常重要的。接下来,我们简要地讨论一下什么样的代码是难以测试的,我们应该如何避免写出难以测试的代码,以及要写出可测试性强的代码的一些最佳实践。

通常一个单元测试主要有三个行为:

这三个行为分别被称为Arrange,ActandAssert。以java为例,一般测试代码如下:

@TestpublicvoidisPalindrome(){//初始化:初始化需要被测试的模块,这里就是一个对象。//也可能没有初始化模块,例如测试一个静态方法。PalindromeDetectordetector=newPalindromeDetector();//调用方法:记录返回值,以便后续验证。//如果方法无返回值,那么我们需要验证它在执行过程中是否对系统的其他部分造成了影响,或产生了副作用。booleanisPalindrome=detector.isPalindrome("kayak");//断言:验证返回结果是否和预期一致。Assert.assertTrue(isPalindrome);}

单元测试的目的是为了验证颗粒度最小的、独立单元的行为,例如一个方法,一个对象。通过单元测试,我们可以确保这个系统中的每个独立单元都正常工作。单元测试的范围仅仅在这个独立单元中,不依赖其他单元。而集成测试的目的是验证整个系统在真实环境下的功能行为,即将不同模块组合在一起进行测试。集成测试通常需要将项目启动起来,并且可能会依赖外部资源,例如数据库,网络,文件等。

1.代码简洁清晰

我们会针对一个单元写多个测试用例,因此我们希望用尽量简洁的代码覆盖到所有的测试用例。

2.可读性强

测试方法的名称应该直截了当地表明测试内容和意图,如果测试失败了,我们可以简单快速地定位问题。通过良好的单元测试,我们可以无需通过debug,打断点的方式来修复bug。

3.可靠性强

单元测试只在所测的单元中真的有bug才会不通过,不能依赖任何单元外的东西,例如全局变量、环境、配置文件或方法的执行顺序等。当这些东西发生变化时,不会影响测试的结果。

4.执行速度快

通常我们每一次打包都会运行单元测试,如果速度非常慢,影响效率,也会导致更多人在本地跳过测试。

5.只测试独立单元

单元测试和集成测试的目的不同,单元测试应该排除外部因素的影响。

1.方法和数据源紧耦合在了一起

2.违反了单一职责原则(SingleResponsibilityPrinciple)

3.方法的职责不清晰

方法签名StringgetTimeOfDay()对方法职责的描述不清晰,用户如果不进入这个api查看源码,很难了解这个api的功能。

4.难以预测和维护

要解决这个问题,通常可以使用依赖注入(控制反转,IoC),控制反转是一种重要的设计模式,对于单元测试来说尤其有效。实际工程中,大多数应用都是由多个类通过彼此的合作来实现业务逻辑的,这使得每个对象都需要获得与其合作的对象(也就是他所依赖的对象)的引用,如果这个获取过程要靠自身实现,那会导致代码高度耦合并且难以测试。那如何反转呢?即把控制权从业务对象手中转交到用户,平台或者框架中。

@TestpublicvoidtestActuateLights(){Calendartime=GregorianCalendar.getInstance();time.set(2018,10,1,06,00,00);SmartHomeControllercontroller=newSmartHomeController(time);controller.actuateLights(true);Assert.assertEquals(time,controller.getLastMotionTime());}到这里,已经可以方便地对其做单元测试了,你认为这段代码已经具有良好的可测试性了吗?

我们仔细看这段开灯关灯的代码:

if(motionDetected&&("Evening".equals(timeOfDay)||"Night".equals(timeOfDay))){//晚上触摸台灯,开灯!BackyardLightSwitcher.Instance.TurnOn();}elseif(getIntervalMinutes(lastMotionTime,nowTime)>1||("Morning".equals(timeOfDay)||"Noon".equals(timeOfDay))){//超过一分钟没有触摸,或者白天,关灯!BackyardLightSwitcher.Instance.TurnOff();}这里通过控制BackyardLightSwitcher这个单例来控制台灯,这是一个全局的变量,意味着每次运行这个单元测试,可能会修改系统中变量的值。换句话说,这个测试产生了副作用。如果有其他的单元测试也依赖了BackyardLightSwitcher的值,那么测试的结果就变得不可控了。因此这个方法依旧不具有良好的可测试性。

java8中引入了函数式和一等公民的概念。我们熟悉的对象是数据的抽象,而函数是某种行为的抽象。

@TestpublicvoidtestActuateLights(){Calendartime=GregorianCalendar.getInstance();time.set(2018,10,1,06,00,00);MockLightmockLight=newMockLight();SmartHomeControllercontroller=newSmartHomeController(time);controller.actuateLights(true,mockLight::turnOn,mockLight::turnOff);Assert.assertTrue(mockLight.turnedOn);}//用于测试publicclassMockLight{booleanturnedOn;voidturnOn(){turnedOn=true;}voidturnOff(){turnedOn=false;}}

现在,我们真正拥有了一个可测试的方法,它非常稳定、可靠,不必担心对系统产生副作用,同时我们也具有了清晰易懂、可读性强、可重用的api。

在函数式编程中,有一个概念叫纯函数,纯函数的主要特点是:

像这样的函数一般具有非常好的可测试性,对它做单元测试方便、且不会出问题,我们需要做的就只是传参数进去,然后检查返回结果。对于不纯的函数,例如某个函数Foo(),它依赖了一个有副作用的函数Bar(),那么Foo()也变成了一个有副作用的函数,最终,副作用可能会遍布整个系统。

THE END
1.VS如何运行测试代码VS如何运行测试代码 简介 现在我们来看看,VS如何运行测试代码。工具/原料 联想2020 Win11 VS 2017 方法/步骤 1 我们可以使用最简单的办法直接点击启动按钮。2 或者我们点击菜单栏中的测试按钮。3 然后我们点击运行二级菜单。4 接下来我们点击所以测试按钮。5 也可以使用它的快捷键。6 同理,还可以使用它的调试命令https://jingyan.baidu.com/article/67662997a06a7d54d51b84e7.html
2.Python中如何实现代码测试问答在Python中,通常可以使用以下几种方法来实现代码测试:1. 单元测试(Unit testing):使用Python内置的unittest模块或第三方库如pytest来编写和运行单元测试,以验证https://www.yisu.com/ask/27942242.html
3.Golang中如何编写test测试代码这个输出结果是否是我们期望的!很多是否都要碰都这种情况,特别是在写一些项目的时候,文件很多代码也多,以前在不知道这样方法的时候就很恼火,以前在遇到这种情况的时候处理方法就是用IDE新建一个环境,然后把要测试的函数直接复制过去,但是这样不仅麻烦很多时候还没有办法做。https://www.jianshu.com/p/3e9aa5d20782
4.MSDN—探索如何创建测试和理解代码MicrosoftLearn创建代码 了解如何创建代码,获取工具,并立即开始学习。 测试代码 阅读有关测试代码的基础知识,获取工具,并立即了解更多信息。 理解代码 学习理解代码的过程,获取工具,并深入了解! 所有开发人员中心 展开表 任务 展开表 理解代码 理解代码涉及多个任务。工具可帮助您以直观形式查看和理解代码的组织、关系和行为。 https://msdn.microsoft.com/zh-cn/gg192976
5.如何创建临时查询,测试运行代码临时查询用于在本地测试代码的实际情况与期望值是否相符或排查代码错误。 背景信息 若您仅需在数据开发(DataStudio),即开发环境,查询数据及相关SQL代码、测试代码的实际运行情况与期望值是否相符,或验证代码的正确性,而无需将数据或SQL代码发布至生产环境并操作生产环境引擎,则可通过 新建临时查询文件实现。https://help.aliyun.com/zh/dataworks/user-guide/create-an-ad-hoc-query
6.四种常见的代码覆盖率测试当您添加新的功能和测试时,增加代码覆盖率百分比可以让您更加确信您的应用程序已经经过了彻底的测试。然而,还有更多的问题有待发现。 四种常见的代码覆盖类型 有四种常见的方法来收集和计算代码覆盖率:函数、行、分支和语句覆盖率。要查看每种类型的代码覆盖率如何计算其百分比,请思考以下计算咖啡成分的代码示例: https://blog.csdn.net/2301_81967508/article/details/144409765
7.谷歌指南:编写可测试的代码谷歌的软件工程师为了保持代码的最佳状态, 写了以下提醒. 缺陷: 构造器做了真正的工作 构造器中的工作包括: 创建/初始化协同类, 和其他service通信, 和设置自身状态的逻辑, 失去了测试的必要, 迫使subclass/mocks去继承不必要的行为. 构造器中太多的工作阻止了测试中的实例化或协同类的修改. https://zhuanlan.zhihu.com/p/146951456
8.JavaIDE如何使用代码覆盖率工具?在软件开发中,代码覆盖率是一个重要的质量指标,它用于衡量测试代码对实际代码的覆盖程度。Java 开发者常常使用 IntelliJ IDEA 这样的 IDE 来进行项目开发和代码测试。然而,有时在使用“Run with Coverage”功能时,会遇到一些问题,导致代码覆盖率无法正常工作。本文将介绍如何在 IntelliJ IDEA 中使用代码覆盖率工具,并https://blog.51cto.com/u_16175520/11996918
9.如何在树莓派4B上通过GoogleMediapipe解决方案实现手势检测6. 编写代码测试 接下来就可以尝试编写代码进行测试了,其中可以参考官方提供Python代码示例来进行学习: import cv2 import mediapipe as mp mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles mp_hands = mp.solutions.hands https://www.eet-china.com/mp/a148964.html
10.如何在Simulink中实现MIL的自动化测试?汽车测试技术自动化测试代码主要包括3个指令:xlsread、sim、xlswrite。 先用xlsread把测试用例的输入和期望输出读取进来;借助for循环,实现每一个测试用例的单独执行,这里会用到sim(‘XX.mdl’),把执行完的结果与期望的结果对比即可获得测试结果;最后把测试结果通过xlswrite写入excel。 https://www.auto-testing.net/news/show-107747.html
11.通过调试器如何在线测试S7SCL源代码?内容预览:描述: 调试器帮助对S7-SCL源代码进行一个简单调试,如果在编译S7-SCL 源代码过程没有错误发生,能够通过利用断点找出程序逻辑错误,并在运行时监视S7-SCL 代码的函数功能。过程如下面表格中描述: https://www.ad.siemens.com.cn/download/DocumentDetail_2719.html
12.静态代码分析测试静态分析如何保障软件质量和软件安全? 提高代码质量并降低缺陷成本 在开发过程的早期防止代码缺陷,以免它们在软件测试的后期阶段造成昂贵的成本。 满足行业功能安全标准 引入支持IS26262、DO-178C、IEC62304、IEC61508、EN50128等功能安全标准的静态分析解决方案。 https://www.parasoftchina.cn/solutions/static-code-analysis/