开发必会的测试知识,Junit+Mock+Assert+DevOps
目录:
为什么要有测试?
测试包括哪些类型?
为什么要有单元测试?
单元测试的七点特征
Mockito & Assert
Junit、TestNG 和 DEVOPS
为什么要有测试?
我之前写过一篇 devops 开发相关的文章 一文理解什么是 devops,可以看到测试在整个开发流程中扮演者什么样的角色。
其中架构演进到第二个阶段敏捷开发的时候,既然我们无法充分了解用户的真实需求是怎样的,将一个大的目标不断拆解,把它变成一个个可交付的小目标,然后通过不断迭代,以小步快跑的方式持续开发。
这个地方就是开发测试混合交替开发的过程,一个个可交付的小目标是需要保证可用的,那么测试就必不可少。包括后来演进到微服务架构,测试更加是 devops 流程里面不可缺少的一环。
测试包括哪些类型?
单元测试目的:用于验证编码单元的正确性,比如测试某个方法逻辑正确性,属于白盒测试,即被测对象内部逻辑对测试者来说是透明的,一般由开发编写。
集成测试:用于验证详细设计,也叫组装测试、子系统测试,是在单元测试的基础上,将涉及到的上下游依赖、数据库、中间件、缓存等都访问真实内容,而不是单元测试的 mock 内容,将涉及到的模块都组装起来形成一个子系统,看这个子系统功能能不能正常服务,满足详细设计要求,属于黑盒测试。
系统测试目的:用于验证概要设计,测试每个系统功能的正确性,属于白盒测试,测试人员来做。
回归测试目的:验证缺陷得到了正确的修复,并且对系统的变更,没有影响以前的功能。一般是通过重新执行所有在前期测试阶段建立的测试用例,来确认问题修改的正确性。
为什么要有单元测试?
开发阶段,其实我们都会有测试,无论是本地捏造数据进行调用接口,还是直接写个 main 函数简单测试下再删掉,都会进行测试,只不过这种方式测的不够全面,各种参数测试结果也没有得到记录,很容易出现某种情况没有考虑完全,或者流程没有测试到的情况。「如果写了单元测试,那么线上跑测试的时候会有测试单元覆盖统计,哪些情况没有测试到一目了然,上线的时候也可以增加信心」,要不然不仅自己心里没底,以后增加了些逻辑需要回归测试的时候,这些测试过程难道又要手动进行一遍?效率也很低。
迭代阶段,一个稳定运行了的系统,如果我们要改点东西,怎么保证他不会影响其他的逻辑呢?将其他测试用例跑一下确认是否可以通过,通不过说明是有问题的。「因此,单元测试是保证重构正确性最有力的手段,有足够的的单测,才能放手大胆的进行代码重构。」
单元测试的七点特征
automated,单元测试需要自动化起来。
我们最早有个概念叫做 daily build,即在开发周期中每天都能发布一个版本,供主管,产品经理,测试,前后端开发所有的人把握项目进度,这就需要持续集成,自动化执行测试,测试毫无疑问的需要融入 devops 自动化流程里面。
线上持续集成单元测试完成之后,可以展示分支覆盖率,行覆盖率,自动化执行时间,单元测试通过率等质量指标,每个公司都会有质量分要求,达不到,不好意思不能上线。
执行够快。
一个项目稍微大一点,写成百上千个测试用例是非常正常的事情,如果跑完这些测试用例需要好几个小时,别说 daily build 了,week build 一周忍受一次都很煎熬,所以需要想办法将这个执行时间降下来,提高交付速度。
不能依赖其他的测试或者其他的测试执行顺序,一个单元测试是独立的。
有一百个测试用例,那么这一百个都应该是独立的,其中九十九个成功了,一个失败就只影响它这一个测试用例,不应该有测试依赖。因此在自动化流程里面,有跑失败了的案例,可以随时重跑这些测试用例,这个操作是个幂等的操作。
「不能依赖外部资源。」
例如数据库权限,文件权限,网络连接,或者一些第三方的 api 接口等资源,测试人员可以想办法通过 mock 的方式给 mock 这些调用,要不然上下游服务一堆依赖,你说你要测试某个接口找到下游服务同学给你权限,然后你捏造很多垃圾数据,上下游服务涉及一堆人这堆人估计要打起来了,这个测试工作简直没办法合作下去了。因此测试不应该依赖外部资源。
单元测试执行是时间和空间透明的。
意思就是,在任何时间和任何环境执行,结果都是一样的,不能说本地可以运行,线上就不可以了,不能依赖运行环境。
单元测试需要有意义。
对一些 bean 的 get,set 方法,hashcode 方法等做单元测试,单纯只是为了凑代码覆盖率,意义是不大的,需要测试一些主流程主分支。
还有数据库 db 操作,如果只是测试 dao 方法假装插入数据,那么是没有意义的,就需要真实插入然后删除。
单元测试不能被当做特殊对待,它和编码同样重要!例如,想做一个上线动作,上线的改动就只是做一些配置文件的变动,这种变动其实是很危险的,应该把 configuration 同样当做源代码来对待,上线之前要做测试公告,要有配置版本管理等。
Mockito & Assert
这里我不打算写这两个工具的具体使用方法,只是介绍,具体使用看看后期要不要安排写一篇。
上面我说单测不能依赖外部资源,但是实际代码里面确实是有这些操作的,那怎么办呢?这就需要 mockito 登场了,类似产品还有 powermock,以及其他语言的对应测试工具,Assert 断言一般是配合着 mock 来使用的,可以判断返回的结果是否是预期。
「mock 的是哪些动作?mock 场景:」
最常用的 mock 场景是外部资源 rpc 调用 拿数据库的连接,增删改查数据 下载文件 发邮件 调用打印机打印文件等
当测试涉及到以上内容的时候,没必要真正去调用这些资源,mockito 可以模拟这些外部资源调用。
mockito 的特点:
快 可靠 自动化
Junit、TestNG 和 DEVOPS
由于这两个常用比较多,但是很多人并不了解详情,这里通过比较的方式介绍一下这两个的功能和区别。
功能比较主要区别:
粗略可以看到,TestNG 功能比 Junit4 更强大,TestNG 支持 Group分组,Parameterized参数化,Dependency依赖测试,并且在 Suite 套件测试中实现不一样导致功能也不一样。后面我会重点分析以下这些套件,分组,都在测试生涯中扮演者什么角色。
Annotation Support 注解支持区别
中英文配合来看,英文可以看到更加具体的,可知 TestNG 支持的注解比 JUnit 丰富很多,JUnit 不支持套件注解、分组注解、以及 @BeforeTest 和 @AfterTest注解,即上面提到的能力不具备。
Parameterized 参数化测试
Parameterized 参数化测试是指单元测试参数值的变化。此功能在JUnit 4和TestNG中都实现,但是实现形式不一样功能不一样。
JUnit 的传参必须将参数传递给构造参数才能初始化类成员作为测试的参数值,参数类的返回类型为“List []”,数据已被限制为 String 或用于测试的原始类型值。
TestNG的参数化测试非常灵活,通过注解 @DataProvider 引入定义了参数的XML文件或类。它可以支持许多复杂的数据类型作为参数值,例如自定义对象,复杂 json 类型等,可能性是无限的。
「因此利用这个可以做数据驱动,QA 和 QE都可以在 XML 文件中提供自己的数据进行测试,我们可以使用不同数据集跑同一个测试用例,获得不同测试结果」。
参数化还有一个好处就是,对于n个不同参数组合的测试,JUnit 4 要写 n 个测试用例。每个测试用例完成的任务基本是相同的,只是受测方法的参数有所改变。TestNG 的参数化测试只需要一个测试用例,然后把所需要的参数加到 TestNG 的 xml 配置文件中。这样的好处是参数与测试代码分离,非程序员也可以修改参数,同时修改无需重新编译测试代码。
Suite Test 套件测试 和 Group Test 分组测试
具体实现表现在 suite test 套件测试上面,“套件测试”是指捆绑几个单元测试并一起运行,测试套件(suite)运行失败,JUnit 4 会重新运行整个测试套件。TestNG 运行失败时,会创建一个 XML 文件说明失败的测试,利用这个文件执行程序,就不会重复运行已经成功的测试。 TestNG可以做捆绑类测试,也可以捆绑方法测试。凭借 TestNG 独特的分组支持,每种方法都可以与一个组合相结合,可以根据功能对测试进行分类(分组)。通过“分组”测试概念,集成测试的可能性是无限制的。例如,我们只能从所有单元测试类中测试“DatabaseFuntion”分组。 TestNG 可以针对失败用例回归测试,增加测试针对性和效率,而 Junit 需要将所有测试用例重新执行; 在自动化测试流程里面,如果测试用例跑失败,一般有个按钮,可以一键重跑失败案例,不需要跑成功案例可节约时间。
「可知,TestNG 被设计应用覆盖所有的测试,单元、功能、端到端、集成测试等主要是套件测试和分组测试能力支持。」
TestNG Dependency test 依赖测试
被依赖测试将在所需方法之前执行,例如购买商品测试需要依赖用户登录成功,使用 @Test(dependsOnMethods = {"funtion name"}) 对登陆成功用例进行依赖,被依赖的用例执行失败后面的用例会直接跳过忽略。「测试结果显示为忽略而不是失败,这样当有成百上千条用例因为被依赖的用例失败而执行不通过时,可以只排查被依赖用例失败原因即可;否则如 Junit4 全部标记为失败的话会造成排查问题和回归测试效率的极大浪费。」
JUnit 4测试的依赖性非常强,测试用例间有严格的先后顺序。前一个测试不成功,后续所有的依赖测试都会失败。TestNG 利用 @Test 的 dependsOnMethods 属性来应对测试依赖性问题。某方法依赖的方法失败,它将被跳过,而不是标记为失败。
TestNG 更适合测试工程师需要的大范围的复杂的集成测试;