测试开发知识


作为测试工程师,目标是要保证系统在各种应用场景下的功能是符合设计要求的。需要考虑的测试用例需要更多、更全面。

黑盒测试方法:

  • 等价类划分方法,将所有可能的输入数据划分为若干个子集。在每个子集中,如果任意一个输入数据对于揭露程序中潜在的错误都具有同等的效果,那么这样的子集就构成了一个等价类。后续只要从每个等价类中任意选取一个值进行测试,就可以用少量具有代表性的测试输入取得较好的测试覆盖结果。
  • 边界值分析方法,选取输入、输出的边界值进行测试。通常大量的软件错误发生在输入或输出范围的边界上,所以需要对边界值进行重点测试。通常选取正好等于刚大于或刚小于边界的值作为测试数据。

上述两种方法相互补充,故通常结合使用。

一个质量过硬的软件系统,除了显式功能性需求之外,其他的非功能性需求即隐式功能性需求也是极其关键的。

显式功能性需求(Functional requirement)指软件本身需要实现的具体功能。

非功能性需求(Non-functional requirement)主要涉及安全性、性能以及兼容性三大方面。这些往往决定软件质量的关键因素。

在绝大多数的软件工程实践中,测试由于受限于时间成本和经济成本,是不可能进行穷尽测试,而是采用基于风险驱动的模式,有所侧重的选择测试范围和设计测试用例,以寻求缺陷风险和研发成本之间的平衡。

每一个解决方案都是下一个问题的来源,要真正理解问题,那至少对自己的解决方案提出三个可能出错的地方。–《你的灯亮着吗》

"好的"测试用例一定是一个完备的集合。它能覆盖所有等价类以及各种边界值,而跟能否发现缺陷无关。需要具备一下三个特征:

  • 整体完备性:"好的"测试用例一定是一个完备的整体,是有效测试用例组成的集合,能够完全覆盖测试需求。
  • 等价类划分的准确性:对于每个等价类都要保证只要其中一个输入测试通过,其他输入也一定测试通过。
  • 等价类集合的完备性:需要保证所有可能的边界值和边界条件都已经正确识别。

常用的测试方法:等价类划分法,边界值分析法、错误推测方法、因果图方法、判定表驱动分析法、正交实验设计方法、功能图分析方法、场景设计方法、形式化方法、扩展有限状态机方法等等。实际使用的主要是前三个。


等价类划分方法

等价类中任意一个输入数据对于揭露程序中潜在错误都具有同等效果。只需从每个等价类中任意选取一个值进行测试,就可以用少量具有代表性的测试输入取得较好的测试覆盖结果。

边界值分析方法

边界值分析是对等待类划分的补充。大量的错误发生在输入输出的边界值上,所以需要对边界值进行重点测试,通常选择正好等于、刚刚大于或刚刚小于边界的值作为测试数据。

错误推测方法

错误推测方法是指基于对被测试软件系统设计的理解、过往经验以及个人直觉,推测出软件可能存在的缺陷,从而有针对性的设计测试用例的方法。该方法强调的是对被测试软件的需求理解以及设计实现的细节把握,当然还有个人能力。

在企业的具体实践中,为了降低对个人能力的依赖,通常会建立常见缺陷知识库,在测试设计的过程中,会使用缺陷知识库作为检查点列表,去帮助优化补充测试用例的设计。


在真实的项目实践中,不同的软件项目在研发生命周期的各个阶段都会有不同的测试类型。

在具体的用例设计中,首先需要搞清楚每一个业务需求所对应的多个软件功能需求点,然后分析出每个软件功能需求点对应的多个软件需求点,最后再针对每个测试需求点设计测试用例。

具体到测试用例本身的设计,有两个关键点需要注意:

  1. 从软件功能需求出发,全面的、无遗漏的识别出测试需求是至关重要的,这将直接关系到用例的测试覆盖率。
  2. 对于识别出的每个测试需求点,需要综合运用等价类划分、边界值分析和错误推测方法来全面的设计测试用例。

经验:

  • 只有深入理解被测试软件的架构,才能设计出“有的放矢”的测试用例集,去发现系统边界以及系统集成上的潜在缺陷。
  • 必须深入理解被测软件的设计与实现细节,深入理解软件内部的处理逻辑。在具体实践中,可以通过代码覆盖率指标标出可能的测试遗漏点。切忌不要以开发代码的实现以依据设计测试用例,应该根据原始需求设计测试用例。
  • 需要引入需求覆盖率和代码覆盖率来衡量测试执行的完备性,并以此为依据来找出遗漏的测试点。

单元测试:对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作,这里的最小可测试单元通常指的是函数或者类。

单元测试的用例是一个“输入数据”和“预计输出”的集合。

“输入数据”:

  • 被测试函数的输入参数
  • 被测试函数内部需要读取的全局静态变量或成员变量
  • 函数内部调用子函数获取或改写的数据
  • 嵌入式系统中,在中断调用时改写的数据

“预计输出”(要严格按照代码的功能逻辑来设计定,不能通过阅读代码来推算预期输出):

  • 被测试函数的返回值
  • 被测试函数的输出函数
  • 被测试函数改写的成员变量或全局变量
  • 被测试函数中进行的文件更新或数据库更新
  • 被测试函数中进行的消息队列更新

注意等价类或边界值,容易在测试时被忽略。

软件测试从小到大的粒度进行划分:单元测试、集成测试、系统测试。

驱动模块:被测函数前的数据准备+调用被测函数+验证相关结果

桩代码:具有隔离和不起的作用,使得被测代码能够独立编译、链接并独立运行,还具有控制被测函数执行路径的作用。

编写桩代码的三个原则

  1. 桩函数具有与原函数完全一致的原型。
  2. 桩函数比较简单,只需保持原函数的声明,加上一个空的实现,目的是通过编译链接。
  3. 要根据测试用例的需要,输出合适的数据作为被测函数的内部输入。

Mock代码和桩代码的区别:测试期待结果的验证。可以理解为关注点的不同。Mock代码关注的是Mock有没被调用,如何调用,调用次数,MOck的调用顺序。对结果验证在Mock函数中。桩代码关注的是被测函数的执行路径。对于结果验证在驱动代码

不是所有代码都要进行单元测试,通常只有底层模块和核心模块。

单元测试款假的选型取决于开发语言。对于桩代码和Mock代码框架选型依据开发所采用的具体技术栈。

需要将单元测试执行、代码覆盖率统计和持续集成流水线做集成,以确保每次代码提交,自动触发单元测试,自动统计代码覆盖率,并决定本次代码提交是否能够被接受。

测试中的困难:

  • 紧密耦合的代码难以隔离
  • 隔离后编译链运行困难
  • 代码可测试性差,可测试性与代码规模呈正比
  • 无法通过桩代码直接模拟系统底层函数的调用
  • 代码覆盖率越往后越难提高

自动化测试的本质就是先写一段代码,然后测试另一段代码。在自动化上,需要权衡利弊。

自动化的缺陷:

  • 自动化只能取代手工测试中执行频率高、机械化高的
  • 自动化成本高,比手动测试脆弱,无法应对被测系统的变化
  • 自动化测试的开发工作量远大于单次的手工测试。只有有效执行次数大于5次,才考虑进行自动化测试
  • 测试效率依赖自动化测试用例的设计与实现质量
  • 初期效率低,后期需要重构

使用自动化测试的项目特点:

  • 需求稳定,不会频繁变更
  • 研发和维护周期长,需要频繁执行回归测试
  • 需要在多种平台上重复执行相同测试的场景
  • 某项测试项目通过手工测试无法实现,或手工成本太高,如性能和压力测试。
  • 被测软件开发规范,具有可测试性。
  • 某些测试用例的自动化需要开发人员预留可测试接口,比如图形验证码。

短期的一次性项目,选择手工探索性测试。中长期项目,对于较稳定的软件功能进行自动化测试,对于变化较大的或需求不明的采用手工测试。

单元测试阶段的“自动化”:

  • 测试用例执行的自动化
  • 用例框架代码生成的自动化
  • 部分测试输入数据的自动化生成
  • 自动桩代码的生成
  • 被测代码的自动化静态分析
  • 测试覆盖率的自动统计与分析

抽桩:比如在单元测试阶段,使用的是桩代码。但是在进行代码集成测试阶段需要调用真实代码,则需要“抽桩”。

代码的静态扫描的目的是识别出违反编码规则或编码风格的代码行。通常是结合项目具体的编码规则和编码风格,有自动化工具通过内建规则和用户自动以规则自动化完成。常用的工具有Sonar和Coverity。

代码行测试覆盖率、分支覆盖率、MC/DC覆盖率等可以帮助衡量单元测试用例集合的充分性和完备性,提供适当增补测试用例以提高测试覆盖率的依据。

代码级集成测试更加关注软件模块之间的接口调用和数据传递。被测函数内部调用的其它函数必须是真实的,不允许使用桩代码代替。单元测试可以。

现在软件追求系统复杂性的解耦,避免“大单体”应用,采用Web Service或者RPC调用的方式来协作完成各个软件功能。现在代码级集成测试基本不做了。

Web Service测试主要是SOAP API和REST API两种。手动常用SoapUI或Postman。

基于代码的API测试用例,通常包含三大步骤:

  1. 准备API调用时需要的测试数据
  2. 准备API调用参数并发起API的调用
  3. 验证API调用的返回结果

Web Service的自动化测试体现在:

  • API测试用例执行的自动化
  • 测试脚手架代码的自动化生成
  • 部分测试输入数据的自动生成
  • Response验证的自动化,其核心思想是自动比较相同API调用的返回结果,并实现出有差异的字段值,比较过程可以通过规则配置来去掉一些动态值
  • 基于SoapUI或者Postman的自动化脚本生成

GUI测试的自动化技术核心思想是基于页面元素识别技术,对于页面元素进行自动化操作,以模拟实际终端用户的行为并验证软件功能的正确性。

需求覆盖率属于传统瀑布模型下的软件工程实践,难以适应当前的敏捷开发。

统计代码覆盖率的根本目的是找出潜在的遗漏测试用例,并有针对性的进行补充,同时还可以识别出代码中那些由于需求变更等原因造成的不可达的废弃代码。

单元测试可以最大化利用打桩技术来提高覆盖率。

MC/DC覆盖率是最高标准的代码覆盖率指标,除了直接关系人生命安全的软件以外,很少有项目会有严格的MC/DC覆盖率要求。

**即使测试用例达到100%的代码覆盖率,产品质量也不能说是万无一失。**在于代码覆盖率是基于现有代码的,对于没有考虑到的某些输入或未处理的情况无法发现。

实现代码覆盖率的统计,最基本的方法就是注入。简单的说就是在被测代码中自动插入用于覆盖率统计的探针代码,并保证插入的探针代码不会给源代码带来任何影响。

实现技术 Instrumentation Source Code Byte Code(主流) Offline:无需修改源代码,但是要在测试开始前对文件进行插桩,并实现生成插过桩的class文件。适用于不支持Java Agent的运行环境,以及无法使用自定义类装载器的情况。无法实时获取。 Inject:直接修改原class文件 Replace:生成新的class文件 On-The-Fly:无需修改源代码,也无需进行字节码插桩。可以在系统不停机的情况下实时手机代码覆盖率信息。但是运行环境必须允许使用Java Agent。 Java Agent:利用执行在main()方法之前的拦截器方法premain()插入探针。实际中需要在JVM添加"-javaagent"启动参数并指定用于实时字节码注入的代理程序。代理程序在装载每个class文件之前,先判断是否插入了探针。没有则将探针插入class文件中。 Class Loader:在每次类加载前,在class文件中插入探针。

作为测试人员,必须深入理解业务,但业务知识不等同于测试能力。

测试开发岗位的核心是测试,开发是为了更好的测试。

测试策略设计能力:对于各种被测软件,能够快速准确的理解需求,并在有限的时间和资源下,明确测试重点和最适合的测试方法。

  1. 测试要具体执行到什么程度;
  2. 测试需要借助于什么工具;
  3. 如何运用自动化测试以及自动化测试框架,以及如何选型;
  4. 测试人员资源如何合理分配;
  5. 测试进度如何安排;
  6. 测试风险如何应对

测试用例设计能力:深入理解被测软件的业务需求和目标用户的使用习惯,熟悉软件的具体设计和运行环境。对常见的缺陷模式、典型的错误类型以及遇到的缺陷,进行归纳总结。阅读好的测试用例。

快速学习能力:1.阅读官方文档 2.理解原理。

探索性测试思维:在执行测试的过程中不断学习被测系统,结合自己的经验猜测和逻辑推理,提出更多的有针对的测试关注点。

缺陷分析能力:

  • 对于已经发现的缺陷,结合发生错误的上下文以及后台日志,可以预测或者定位缺陷的发生原因,甚至可以明确指出具体出错的代码行,由此可以大幅缩短缺陷的修复周期,并提高开发工程师对于测试工程师的认可以及信任度;

  • 根据已经发现的缺陷,结合探索性测试思维,推断同类缺陷存在的可能性,并由此找出所有相关的潜在缺陷;

  • 可以对一段时间内所发生的缺陷类型和趋势进行合理分析,由点到面预估整体质量的健康状态,并能够对高频缺陷类型提供系统性的发现和预防措施,并以此来调整后续的测试策略

自动化测试技术:自动化是手段。

缺陷报告的组成:

  • 缺陷标题:概括性描述,在什么情况下发生了什么问题。应该尽量描述问题本质,而非停留在问题表面。
  • 缺陷概述:除了描述清楚缺陷,还可以扩展,比如描述同一类型的缺陷可能出现的所有场景。目的是清晰简洁的描述缺陷,使开发工程师能够聚焦缺陷的本质
  • 缺陷影响:缺陷引起的问题对用户或者业务的影响范围以及严重程度。决定缺陷的优先级和严重程度。
  • 环境配置:详细描述测试环境的配置细节。通常只描述那些重现缺陷的环境的敏感信息。
  • 前置条件:测试步骤前系统应该处在的状态,目的是减少缺陷重现的步骤
  • 缺陷重现步骤:**目的在于用简洁的语言向开发工程师展示缺陷重现的具体操作步骤。**确保缺陷的可重现性,找到最短的重现路径。
  • 期望结果与实际结果:在描述重现步骤的过程中,需要明确说明期待结果(应该发生什么)和实际结果(发生了什么)。
  • 优先级和严重程度:严重程度是缺陷本身的属性,优先级是缺陷的工程属性,随着项目进度、解决缺陷的成本等因素而变动。
  • 变通方案:提供一种临时绕开当前缺陷而不影响产品功能的方式。
  • 根原因分析:即RCA,定位出问题的根本原因,清楚的描述缺陷产生的原因。
  • 附件:为缺陷的存在提供必要的证据支持,如:界面截图、测试用例日志、服务端日志、GUI测试视频等。

测试计划的组成:

  • 测试范围:描述被测对象和主要的测试内容,明确“测什么”和“不测什么”
  • 测试策略:明确测试的重点和各项测试的先后顺序,采用什么样的测试类型和测试方法,不仅要给出为什么选用这个测试类型,还要详细说明具体的实施方法
    1. 功能测试:哪些测试点适合采用自动化测试,并采用什么样的框架技术。需要手工测试的测试点,决定采用什么类型的测试用例设计方法,如何准备相关的测试数据。
    2. 兼容性测试:既有产品测试Top30%的移动设备以及IOS/Android版本列表。全新产品:通过TalkingData这样的网站查看目前主流的移动设备、分辨率大小、IOS/Android版本等信息
    3. 性能测试
  • 测试资源:谁来测,在哪测。
  • 测试进度:描述各类测试的开始时间、所需工作量、预计完成时间,并以此为依据来建议最终产品的上线发布时间。
  • 测试风险评估:预估整个测试过程中可能存在的潜在问题,以及当这些风险发生时的应对策略。

扩展知识:网络架构的核心知识(性能测试、稳定性测试、全链路压测、故障切换Failover测试、动态集群容量伸缩测试、服务降级测试、安全渗透测试、Memcached分布式缓存集群、缓存击穿、缓存雪崩、缓存预热、缓存集群扩容局限性、可伸缩性架构设计、负载均衡、数据库读写分离、故障切换、动态集群容量伸缩、服务降级)、容器技术(Selenium Grid、Docker、Kubernetes)、云计算技术(Appium+Selenium Grid)、DevOps思维(Jenkins,组合各种Plugin来完成流水性搭建,提供高效的CI/CD)、前端开发技术。

通常情况下,互联网产品要求回归测试的执行时间不能超过四个小时。

缩短测试执行时间的方法:

  • 引入测试的并发执行机制,用包含大量测试节点的测试执行集群来并发执行测试用例。
  • 改进测试策略设计。
    1. 单元测试
    2. API测试
    3. GUI测试

传统软件通常采用金字塔模型的测试策略,现今的互联网产品往往采用菱形模型:以中间层的API测试为重点做全面的测试;轻量级的GUI测试,支付高最核心直接影响主营业务流程的E2E场景;最上层的GUI测试通常利用探索式测试思维,以人工测试的方式发现尽可能多的潜在问题。


文章作者: 不二
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 不二 !
  目录