开发者测试基础知识:
单元测试&TDD
TDD:tdd(test-driven development测试驱动开发)的开发模式。
测试驱动开发的基本思想就是在开发功能代码之前,先编写测试代码,然后只编写使测试通过的功能代码,从而以测试来驱动整个开发过程的进行。这有助于编写简洁可用和高质量的代码,有很高的灵活性和健壮性,能快速响应变化,并加速开发过程。
-
先写测试代码,并执行,得到失败结果
-
写实现代码让测试通过
-
重构代码,并保证测试通过。
-
反复实行这个步骤 测试失败 -> 测试成功 -> 重构
-
测试代码先于实现代码,不允许有没有测试代码的实现代码。
这个字面意思还是很好理解。在这样的开发方式中,你所开发的功能绝对会被测试保护的。
如下图。
有一些开发者可能喜欢先写代码,后写测试。从tdd的角度来说,这样何容易造成上图的结果。因为当你的实现代码变复杂了之后,你可能遗漏一些测试的case。
另外,先开发后测试的另一个缺点是写测试的难度会变高。这应该也很好理解,当你设计程序时,如果你没有考虑可测试性的要素,很可能最终的代码会变得超难测。比如一个没有返回值,但有副作用的函数,写测试就会比较麻烦。(当然我不是说写函数一定要有返回值 (´ε`;)) -
好的设计是不断的重构中实现的,想要在写代码前就作出好的设计是很有难度的。与其在最初去做好的设计,不如不断的重构,使代码进化。
其实这个思想应该是Emergent Design。本身和tdd并无直接关系。但由于培训师讲师非常提倡这个思想,结果不慎被洗脑,在这里讲一下。
这个思想的缘由大致是,程序用来实现具体的业务逻辑,当这也业务逻辑是复杂的情况下,理解业务逻辑是需要花费时间的。
当程序员开始开发,处于程序设计阶段时,对业务地理解是相对少和浅的。对业务的理解是随着开发的推进而增加。
这种情况特别在维护现有产品时更容易显现。(一个原本已经能够运行的成品,因为一些功能的修改或增加,突然一些地方就跑不了了。。。仔细一查,发现了一些不为人知的业物逻辑。。。orz)
当你在对业务的理解相对少的前期阶段,做出好的设计是很困难的。而在开发有一定进展后,对业务有了更好的理解,这时候做出的设计就会更符合需求更加合理。
另外ttd(也不一定源于tdd)把产品品质分成两种。
外部品质: 保证程序根据需求运行,不出错
内部品质: 产品的设计,可维护性等
- 不做过度的设计(over engineering)
这个观点其实和Emergent Design也是关联的。程序开发的前期阶段为了程序更好的扩展性,往往做了一些超越目前需求的设计。貌似是有数据支持,不过因为本人太懒,懒得找。维基百科上说是,这些为了扩展性所做的设计,最终得到使用的不足10%,所以90%的时间都会被浪费。
另外具有扩展型的设计,因为这些设计的部分很可能现阶段没有被使用,反而会迷惑其他的开发者。(比如你设计了一张数据表,然后为了将来不对数据表做更改,加了一列 colum_for_future_extension)
对此还有一个标语专门提倡这种观点。YAGNI。You ain’t gonna need it!
所以tdd所期望的是程序的实现能与现阶段的需求完全契合,不做过度的设计。你要做的是根据现在的需求写测试,让测试通过,并此需求上把代码设计的更好更合理。因为测试为先,测试又根据需求来写。这样的话会比较难产生over engineering。
单元测试
单元测试的目的是让开发人员相信他们的应用程序的特定部分以开发人员期望的方式运行。这对回归测试非常有价值。如果您修改了单元测试所涵盖的代码,那么您可以使用这些测试来立即确定您是否已经破坏了现有的功能。
那么TDD测试和单元测试有什么不同呢?与单元测试不同,TDD测试用于驱动应用程序的设计。TDD测试用于表示在实际编写应用程序代码之前,应用程序代码应该做什么。
测试驱动开发源于对瀑布开发的反应。测试驱动开发的一个重要目标是肯特·贝克所说的增量设计和马丁·福勒所说的进化设计。应用程序不是一次设计一个应用程序,而是一个一个测试地递增设计。
在实践测试驱动开发时,您可以通过反复执行以下步骤来开发应用程序:
创建失败的测试
编写足够的代码通过测试
重构代码以改进其设计
这个过程被称为红色/绿色/重构,因为单元测试框架显示失败测试的红色条和通过测试的绿色条。
当实践测试驱动开发时,第一步总是创建一个失败的测试。您使用测试来表达您希望代码如何表现。例如,在构建论坛应用程序时,您可以从编写一个测试开始,验证您是否可以向论坛发布新消息。
当您编写TDD测试时,您编写测试时没有预先做出任何设计决策。福勒写道:
然而,古典主义者认为,重要的是只考虑外部接口发生了什么,并且在完成测试之前不考虑实现
http://martinfowler.com/articles/mocksArentStubs.html#DrivingTdd
当编写单元测试时,你应该只考虑你想要你的应用程序做什么。写完测试后,就可以做出实现决定了。最后,在实现了足够多的代码之后,您需要考虑如何重构应用程序以获得更好的设计。
与单元测试不同,TDD测试可能一次测试多个代码单元。福勒写道:
在极限编程中,单元测试通常不同于经典的单元测试,因为在极限编程中,您通常不会单独测试每个单元。
Fowler http://www.artima.com/intv/testdriven4.html
随着应用程序设计的发展,最初包含在一个类中的代码可能最终会出现在多个类中。与单元测试不同,TDD测试可以测试跨多个类的代码。
我觉得两者主要是目的不同,同时发起的时间也不同。
常见的软件测试术语(UT、IT、ST、静态、动态测试、黑盒白盒测试)
UT
一、UT(单元测试,Unit Test):
单元测试任务包括:
1、模块接口测试;
2、模块局部数据结构测试;
3、模块边界条件测试;
4、模块中所有独立执行通路测试;
5、模块的各条错误处理通路测试。;
IT
也称系统集成测试(System Integration Test)或结合测试,集成测试阶段是以黑盒法为主,在自底向上集成的早期,白盒法测试占一定的比例,随着集成测试的不断深入,这种比例在测试过程中将越来越少,渐渐地,黑盒法测试占据主导地位。
ST
(系统测试,System Test):从技术角度看,系统测试是整个测试阶段的最后一步,所有的开发和测试在这一点上集中表现为生成一个具有一定功能的软件系统。该阶段主要对系统的准确性及完整性等方面进行测试。
主要进行:
功能确认测试、运行测试、强度测试、恢复测试、安全性测试等。系统测试的测试人员由测试组成员(或质量保证人员)或测试组成员与用户共同测试。在整个系统开发完成,即将交付用户使用前进行。在这一阶段,完全采用黑盒法对整个系统进行测试。
BBIT
BBIT为模块间接口测试,验证模块之间的接口能不能配合,有时和联调混在一起,其实目的并不相同。BBIT的目的,是根据系统设计对系统的分解,从已通过验证的模块开始,逐层向上集成,得到一个可运行的系统。而联调一般涉及软件、硬件或者不同产品间的配合测试。MST和BBIT可以归到“模块级” 的测试,一个验证模块,一个验证模块间的接口。
以上UT/IT/MST/BBIT一般由开发人员完成,系统基本可以运行起来了,测试人员可以开展SDV、SIT、SVT了。
SDV
SDV虽然属于测试人员开展的系统测试,但是有点偏灰盒测试,因为SDV验证各子系统的配合是否满足设计需求(DR),对内部的实现还是关注的,验证多个模块集成以后是否满足设计需求。
SIT
SIT也是验证设计需求是否得以满足,与SDV不同的是,SIT完全把系统当作一个黑盒来测试,不关心内部具体的实现。实际应用中,SDV和SIT 虽然都属于系统一级的测试,往往由不同项目组(子系统)的测试人员分别测试,他们只关注各自的子系统,所以还是把SDV和SIT归为“子系统级”的测试比较好。
SIT也是验证设计需求是否得以满足,与SDV不同的是,SIT完全把系统当作一个黑盒来测试,不关心内部具体的实现。实际应用中,SDV和SIT 虽然都属于系统一级的测试,往往由不同项目组(子系统)的测试人员分别测试,他们只关注各自的子系统,所以还是把SDV和SIT归为“子系统级”的测试比较好。
SVT
SVT是验收测试,其测试对象是产品包需求OR。产品包需求给出了产品的范围,从产品可能的应用环境的角度刻画系统,SVT的目的就是确认(或验收)产品包需求给出的各种应用场景产品均能满足。
产品包需求不考虑内部实现的差异,SVT也是从整个系统的角度考虑包需求的各种应用场景,属于“系统级”的测试。
各个级别的测试描述完毕,回头再看看这个分层测试的模型图,不难发现以下几个特征:
1)基于系统架构的分解结构(系统-子系统-模块-单元),开发按照自顶向下的顺序逐层设计,测试按照自底向上的顺序逐层验证,这个分解结构在每一层或每一个阶段,将开发和测试过程统一起来。
2)在每一层,测试的对象是开发相应阶段设计的输出(包括需求和这个阶段的设计文档),测试的目的与开发相应阶段设计的思路是相辅相成的,所以决定每个阶段的测试如何开展、评价一个测试过程时,如果离开开发过程,只谈测试自身的话,是不系统、不全面的。
3)除了“系统级”的SVT测试以外,其他各层的测试均包含两个方面:一是对这个层每个构件的测试,有n个构件就要测试n次,二是这n个构件之间接口的测试。例如:nSDV(每个测试项目组的SDV是一个SDV)和SIT、nMST(每个开发项目组的MST是一个MST)和BBIT、nUT和IT。
UAT(验收测试,User Acceptance Test):验收测试是向未来的用户表明系统能够像预定要求那样工作。
经集成测试后,已经按照设计把所有的模块组装成一个完整的软件系统,接口错误也已经基本排除了,接着就应该进一步验证软件的有效性,这就是验收测试的任务,即软件的功能和性能如同用户所合理期待的那样。
静态测试和动态测试
1、测试部分的不同
静态测试是指测试不运行的部分:只是检查和审阅,如规范测试、软件模型测试、文档测试等。动态测试是通常意义上的测试,也就是运行和使用软件。
2、测试方式不同
静态测试,通过评审文档、阅读代码等方式测试软件称为静态测试,通过运行程序测试软件称为动态测试。
3、测试方法不同
静态测试是指不用执行程序的测试,它主要采取方案—代码走查、技术评审、代码审查的方法对软件产品进行测试。动态测试主要通过构造测试实例、执行程序、分析程序的输出结果这三种方法来对软件进行测试。
静态测试主要包括:(1)代码检查:代码会审、代码走查、桌面检查;(2)静态结构分析;(3)代码质量度量。
动态测试主要包括:(1)黑盒测试:又称功能测试。这种方法把被测软件看成黑盒,在不考虑软件内部结构和特性的情况下测试 软件的外部特性。(2)白盒测试:又称结构测试。这种方法把被测软件看成白盒,根据程序的内部结构和逻辑设计来设计测试实 例,对程序的路径和过程进行测试。
————————————————
版权声明:本文为CSDN博主「大西瓜不甜」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mid_Faker/article/details/105937155
黑盒白盒测试
白盒测试也称为结构测试,主要用于检测软件编码过程中的错误。逻辑,内部的。
黑盒测试又称为功能测试,主要检测软件的每一个功能是否能够正常使用。在测试过程中,将程序看成不能打开的黑盒子,不考虑程序内部结构和特性的基础上通过程序接口进行测试,检查程序功能是否按照设计需求以及说明书的规定能够正常打开使用。整体,宏观的。
开发者测试定义以及价值
开发者测试定义以及价值
开发者测试(DT),是指开发者所做的测试,有别于专职测试人员进行的测试活动。
DT目标是在软件交付转验收测试前,发现和解决绝大多数代码缺陷,而其理论依据是业界
研究反复揭示的“前端发现问题的代价远小于后端”。
开发者测试优势
- 测试环境依赖低
- 功能覆盖成本低、质量高
- 作为防护网,自动化程度高、反馈快,高效可信重构的前提
- 开发者测试能有效降低前端缺陷,代码质量更高,发布周期更快。
开发者测试是现代软件工程中非常重要的一环,敏捷开发、主干开发这些先进的项目管理方法和流程都基于完善的开发者测试。
当每个月甚至每周都要交付一个版本时,不可能投入大量的测试工程师来进行大规模的系统级别测试,这时候就需要把整个测试金字塔中的绝大部分测试通过自动化来完成。
可测试性
软件可测试性(Software testability)是指一个软件工件(软件系统、模组、需求文件或设计文件等)在一给定的测试环境下,可支援测试的程度。
软件测试中的可测性一般是指对系统的可控性、可观测性进行的评估,借以反映系统设计、实现对测试的友好程度和相应的测试成本。
可测性在测试阶段会对系统的测试成本及后期产品使用过程中的易用性、维护成本等会产生重大影响。如何提高可测性成为软件生命周期特别是前期(设计阶段、coding阶段)重要的一环。
作者:大力水手112233
链接:https://www.jianshu.com/p/3ce44670dd21
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
测试分层
将测试分为三个层次,分别是UI层、service层和unit层。
测试设计:
掌握基本的测试设计方法(等价类、边界值、单一逻辑覆盖)
基本的测试设计方法
1、等价类
2、边界值
3、场景法
4、判定表
5、因果图
6、错误推断法
7、正交测试法(正交表)
灵活应用多种覆盖技术的组合进行用例设计(AC、EC、BC、N-Wise)
多种覆盖技术组合(AC、EC、BC、N-Wise)
掌握白盒覆盖分析方法以及覆盖率分析方法
白盒覆盖分析方法和覆盖率分析方法
测试覆盖率
测试覆盖率:用于确定测试所执行到的覆盖项的百分比。其中的覆盖项是指作为测试基础的一个入口或属性,比如语句、分支、条件等。测试覆盖率可以表示出测试的充分性,在测试分析报告中可以作为量化指标的依据,测试覆盖率越高效果越好。单覆盖率不是目标,只是一种手段。
测试覆盖率包括功能点覆盖率和结构覆盖率。
功能点覆盖率大致用于表示软件已经实现的功能与软件需要实现的功能之间的比例关系。
结构覆盖率包括语句覆盖率、分支覆盖率、循环覆盖率、路径覆盖率等。
逻辑覆盖法
根据覆盖目标的不同,逻辑覆盖又可以分为语句覆盖、判定覆盖、条件覆盖、判定\条件覆盖、组合覆盖和路径覆盖。
-
语句覆盖:选择足够多的测试用例,使得程序中的每个可执行语句至少执行一次。
-
判定覆盖:通过执行足够的测试用例,使得程序中的每个判定至少都获得一次“真”值和“假”值,也就是使程序中的每个取“真”分支和取“假”分支至少均经历一次,也称为“分支覆盖”。
-
条件覆盖:设计足够多的测试用例,使得程序中每个判定包含的每个条件的可能取值(真/假)都至少满足一次。
-
判定/条件覆盖:设计足够多的测试用例,使得程序中每个判定包含的每个条件的所有情况(真/假)至少出现一次,并且每个判定本身的判定结果(真/假)也至少出现一次。----满足判定/条件覆盖的测试用例一定同事满足判定覆盖和条件覆盖。
-
组合覆盖:通过执行足够的测试用例,使得程序中每个判定的所有可能的条件取值组合都至少出现一次。----满足组合覆盖的测试用例一定满足判定覆盖、条件覆盖和判定/条件覆盖。
-
路径覆盖:设计足够多的测试用例,要求覆盖程序中所有可能的路径
六种覆盖标准发现错误的能力呈由弱到强的变化
语句覆盖:每条语句至少执行一次。
判定覆盖:每个判定的每个分支至少执行一次。 (包含语句覆盖,每个判断T、F各一次)
条件覆盖:每个判定的每个条件应取到各种可能的值。 (包含语句覆盖,每个条件T、F各一次)
判定/条件覆盖:同时满足判定覆盖和条件覆盖。
条件组合覆盖:每个判定中各条件的每一种组合至少出现一次。
路径覆盖:使程序中每一条可能的路径至少执行一次。
一般通过被测试的软件产品需求、功能点、测试用例数或程序代码行等来进行计算。
1.功能覆盖率= 至少被执行一次的测试功能点数/ 测试功能点总数 (功能点)
2.需求覆盖率= 被验证到的需求数量 /总的需求数量 (需求)
3.覆盖率= 至少被执行一次的测试用例数/ 应执行的测试用例总数 (测试用例)
测试的实现与执行:
掌握测试用例编码的FIRST原则、测试用例写作基本规范,能够使用框架完成基本的单元测试写作
测试用例编码的FIRST原则
Fast,
Fast 快
程序员的每个单元测试应该很快能够被执行完,如果要给一个标准的话,应该是秒级的。单元测试只测试自己的代码,不应该像集成测试那样,连接外部的组件。如果是和时间相关的测试,不应该真的等待时间流逝再看结果,而应该使用 Mock 来模拟时间。
Isolated/Independent,
Isolated / Independent 隔离 / 独立
每个单元测试不应该依赖于其他的单元测试结果。它应该能够独立运行,而不是要求某些单元测试完成以后才能完成这个单元测试。同样的某个单元测试的结果,也不应该影响其他单元测试的运行结果。如果需要一些配置,这些配置应该在本单元测试的初始化阶段完成。
Repeatable,
Repeatable 可重复
单元测试无论在任何环境下都应该得到相同的结果,而不应该受到数据库,网络等环境因素的影响。单元测试的结果只跟我们的代码有关,而不应该和环境有关。如果单元测试失败,它应该是我们代码的某个地方有问题,或者是初始值设置的问题。这样在拿到单元测试的结果的时候,我们就可以专注于我们的代码,而不是环境因素。
Self-Validating,
Self-Validating 自我校验
单元测试只会返回一个布尔结果,要么成功,要么失败,而不是要程序员自己检查运行结果以后才能判断是否成功。在单元测试中,我们应该加上断言,比如 Assert.IsTrue, Assert.AreEqual等,这些断言直接决定测试结果是否成功。
Timely/Thorough.
如果我们的开发方式是测试驱动开发 (TDD) ,那么这一条指的是单元测试应该在生产代码 (production code) 完成前完成。如果你不用 TDD,这一条指的是单元测试要达到下面的标准
覆盖所有基本路径 (Happy Path)
边界条件
安全问题
覆盖所有可能的功能性用例,而不只是应付 100% 代码覆盖率的要求
测试用例写作基本规范
测试用例规范主要考虑到:
1.测试的覆盖;
2.测试用例的维护;
3.测试用例的易读性。
编写测试用例的8大要素有:用例编号,所属模块,测试标题,重要级别,前置条件,测试输入,操作步骤,预期结果。以及编写测试用例时的注意事项 。
使用框架完成基本的单元测试
掌握golang testing包的基本用法,如测试用例的编写、运行、结果分析、parallel、example、benchmark等基本使用方法
掌握gomark测试框架的mock使用方法
golang testing包
testing 包是 go 中提供自动化测试的包,和命令 go test 配合使用,能够自动执行匹配到的函数。
测试函数一般是这样的:
func TestXxx(*testing.T)
测试函数需要满足一定的条件才能被执行,就像上面的那样,以Test开头,然后接一个以大写字母开头的单词,函数参数是*testing.T
测试函数所在的文件也需要满足一定的条件:文件名需要以_test.go结尾,这样的文件在go build的时候不会包含,但是可以在go test的时候调用到
测试用例的编写、运行、结果分析
parallel\example\benchmark
func BenchmarkXxx(*testing.B)
和上面那个TestXxx差不多,以Benchmark开头,并接一个大写字母开头的单词,函数参数是*testing.B
这样的测试函数是压力测试函数,可以使用go test并且加上-bench参数的时候,被调用到
func BenchmarkHello(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Sprintf("hello")
}
}
压力测试函数必须运行 b.N 次目标代码,在压力测试函数运行期间,b.N 会动态的调整,直到基准测试功能持续足够长时间以可靠地计时为止
压力测试函数的输出类似于:
BenchmarkHello 10000000 282 ns/op
这个的意思是压力测试函数以平均 282ns 每次的速度运行了 10000000 次
如果压力测试函数需要 setup 一些操作,那么需要调用一下b.ResetTimer(),示例:
func BenchmarkBigLen(b *testing.B) {
big := NewBig()
b.ResetTimer()
for i := 0; i < b.N; i++ {
big.Len()
}
}
如果压力测试需要测试并发,那么需要使用到RunParallel函数,示例:
func BenchmarkTemplateParallel(b *testing.B) {
templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
b.RunParallel(func(pb *testing.PB) {
var buf bytes.Buffer
for pb.Next() {
buf.Reset()
templ.Execute(&buf, "World")
}
})
}
测试函数以Example开头,接一个大写字母开头的单词,没有函数参数;然后将函数内部以// Output:开头下面的注释和标准输出进行比较(忽略前后的空格)。
func ExampleSalutations() {
fmt.Println("hello, and")
fmt.Println("goodbye")
// Output:
// hello, and
// goodbye
}
有的时候输出是无需的,比如并发的时候,这个时候就需要使用// Unordered output:了:
func ExamplePerm() {
for _, value := range Perm(4) {
fmt.Println(value)
}
// Unordered output: 4
// 2
// 1
// 3
// 0
}
使用 Example 的时候有一些函数命名约定:函数 F,类型 T,类型 T 上面定义的方法 M
func Example() { ... }
func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }
如果一个函数需要有多个 Example 函数,可以以下划线作为分割添加后缀
func Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }
子测试和子压力测试
testing.T和testing.B的 Run 方法允许定义子测试和子压力测试,而不需要定义两个测试
主测试
在有些测试中,需要在所有的测试之前做一些 setup,在所有的测试之后做一些 teardown,所以需要一个主测试来控制这些:
gomark测试框架
1、GoMock简介
GoMock是由Golang官方开发维护的测试框架,实现了较为完整的基于interface的Mock功能,能够与Golang内置的testing包良好集成,也能用于其它的测试环境中。
GoMock测试框架包含了GoMock包和mockgen工具两部分,其中GoMock包完成对桩对象生命周期的管理,mockgen工具用来生成interface对应的Mock类源文件。
调试与定位
掌握Debugger(DLC,GDB)的基本设置与常用命令。包括单独执行、跳入函数
跳出函数、设置断点、查看变量、查看寄存器、查看调用栈等。
通过合理的日志协助调试和定位问题