测试基础知识篇(11讲)
01 你真的懂测试吗?从“用户登录”测试谈起
测试用例设计框架
基于功能性需求和非功能性需求思考:
功能性需求使用等价类划分、边界值分析、错误推断法设计用例
非功能性需求考虑安全(信息的保存、传输是否加密,被gj,bl破解)、性能(多快好省,响应时间,tps,资源占用)、兼容性(同一浏览器不同版本,不同浏览器,不同移动端,不同屏幕大小,不同分辨率)
02 如何设计一个“好的”测试用例?
设计用例的方法
等价类划分、边界值分析、错误推测法(根据个人经验推测可能出现的错误)
其他注意点:
bug漫游:一个bug出生地漫游到其他提供相同功能地方,验证是否存在相同的bug
设计用例的经验
1)深入理解被测软件的架构,发现系统边界以及系统集成的潜在缺陷
*数据库连接方式、数据库的读写分离、消息中间件 Kafka 的配置、缓存系统的层级分布、第三方系统的集成*
2)理解被测软件的设计与实现细节,深入理解软件内部的处理逻辑
3)引入需求覆盖率和代码覆盖率来衡量测试执行的完备性,并以此为依据找出遗漏的测试点
03 什么是单元测试?如何做好单元测试?
单元测试是指,对软件中的最小可测试单元在于程序其他部分相隔离的情况下进行检查和验证的工作,这里的最小可测试单元通常指函数或者类。
如何做好单元测试
第一、代码的基本特征与产生错误的原因
要做到代码功能逻辑正确,必须做到分类正确且完备无遗漏,同时每分类的处理逻辑必须正确。
第二、单元测试用例详解
单元测试的用例是一个“输入数据”和“预计输出”的集合。
输入数据:被测函数的输入参数、需要读取的全局静态变量、成员变量、调用子函数获得的数据、调用子函数改写的数据
预计输出:被测函数的返回值、输出参数,改写的成员变量、全局变量,更新的文件、数据库、消息队列
第三、驱动代码、桩代码和mock代码
驱动代码是用来调用被测函数的,而桩代码和 Mock 代码是用来代替被测函数调用的真实代码的。
驱动代码(Driver)指调用被测函数的代码
桩代码(Stub)是用来代替真实代码的临时代码。桩代码的应用首先起到了隔离和补齐的作用,使被测代码能够独立编译、链接,并独立运行。同时,桩代码还具有控制被测函数执行路径的作用。
Mock 代码和桩代码的本质区别是:测试期待结果的验证(Assert and Expectiation)。对于 Mock 代码来说,我们的关注点是 Mock 方法有没有被调用,以什么样的参数被调用,被调用的次数,以及多个 Mock 函数的先后调用顺序。所以,在使用 Mock 代码的测试中,对于结果的验证(也就是 assert),通常出现在 Mock 函数中。对于桩代码来说,我们的关注点是利用 Stub 来控制被测函数的执行路径,不会去关注 Stub 是否被调用以及怎么样被调用。所以,你在使用 Stub 的测试中,对于结果的验证(也就是 assert),通常出现在驱动代码中。
如何开展单元测试?
- 底层模块和核心模块才会用单元测试
- 框架选型
- 引入代码覆盖率工具
- 将单元测试执行、代码覆盖率统计和持续集成流水线做成集成,每次提交代码后以“单元测试通过率”和“代码覆盖率”为标准决定本次代码提交是否通过
04 为什么要做自动化测试?什么样的项目适合做自动化测试?
为什么需要自动化测试?
- 自动化测试可以替代大量的手工机械重复性操作,测试工程师可以把更多的时间花在更全面的用例设计和新功能的测试上;
- 自动化测试可以大幅提升回归测试的效率,非常适合敏捷开发过程;
- 自动化测试可以更好地利用无人值守时间,去更频繁地执行测试,特别适合现在非工作时间执行测试,工作时间分析失败用例的工作模式;
- 自动化测试可以高效实现某些手工测试无法完成或者代价巨大的测试类型,比如关键业务 7×24小时持续运行的系统稳定性测试和高并发场景的压力测试等;
- 自动化测试还可以保证每次测试执行的操作以及验证的一致性和可重复性,避免人为的遗漏或疏忽。
自动化测试的“坑”
- 不能取代手工测试,只能替代手工测试中执行频率高、机械化的重复步骤。
- 无法应对 被测系统的变化
- 自动化测试用例的开发工作量远大于单次的手工测试,只有当开发完成的测试用例的有效执行次数大于等于5次时,才能收回自动化测试的成本。
- 测试的效率很大程度上依赖于自动化测试用例的设计以及实现质量,不稳定的自动化测试用例实现比没有自动化更糟糕。
- 自动化测试初期,用例开发效率会比较低。
什么样的项目适合自动化测试?
- 需求稳定,不会频繁变更
- 研发和维护周期长,需要频繁执行回归测试
- 需要在多种平台上重复运行相同测试的场景
- 某些测试项目通过手工测试无法实现,或者手工成本太高(性能和压力测试)
- 被测软件的开发较为规范,能够保证系统的可测试性(某些用例的自动化要求开发人员在产品中预留可测试性接口)
自动化测试可以把人力从机械重复的测试工作中解脱出来,将更多精力放在新功能的测试和更全面的测试用例设计上,所以自动化测试技术是为业务测试服务,要将更多精力放在业务测试上才对。
05 你知道软件开发各阶段都有哪些自动化测试技术吗?
单元测试的自动化技术
- 用例框架代码生成
- 部分测试输入数据自动生成
- 桩代码生成
- 被测代码的自动化静态分析(Sonar 和 Coverity)
代码级集成测试的自动化技术
代码级集成测试与单元测试最大的区别只是,代码级集成测试中被测函数内部调用的其他函数必须是真实的,不允许使用桩代码代替,而单元测试中允许使用桩代码来模拟内部调用的其他函数。
Web Service 测试的自动化技术(接口测试)
java :REST Assured。参考资料:REST-Assured,接口自动化的 “瑞士军刀“- 初识篇_rest-assured pom_软件测试狂阿沐的博客-CSDN博客
python : pytest、httprunner
因为通常不会对response中的所有字段进行assert,所以Response 验证自动化的核心思想是自动比较两次相同 API 调用的返回结果,并自动识别出有差异的字段值,比较过程可以通过规则配置去掉诸如时间戳、会话 ID(Session ID)等动态值。
GUI 测试的自动化技术
- 对于传统 Web 浏览器的 GUI 自动化测试,业内主流的开源方案采用 Selenium,商业方案采用 Micro Focus 的 UFT(前身是 HP 的 QTP);
- 对于移动端原生应用,通常采用主流的 Appium,它对 iOS 环境集成了 XCUITest,对 Android 环境集成了 UIAutomator 和 Espresso。
06 你真的懂测试覆盖率吗
代码覆盖率指标
- 行覆盖率又称为语句覆盖率,指已经被执行到的语句占总可执行语句(不包含类似 C++ 的头文件声明、代码注释、空行等等)的百分比。这是最常用也是要求最低的覆盖率指标。实际项目中通常会结合判定覆盖率或者条件覆盖率一起使用。
- 判定覆盖又称分支覆盖,用以度量程序中每一个判定的分支是否都被测试到了,即代码中每个判断的取真分支和取假分支是否各被覆盖至少各一次。比如,对于 if(a>0 && b>0),就要求覆盖“a>0 && b>0”为 TURE 和 FALSE 各一次。
- 条件覆盖是指,判定中的每个条件的可能取值至少满足一次,度量判定中的每个条件的结果 TRUE 和 FALSE 是否都被测试到了。比如,对于 if(a>0 && b>0),就要求“a>0”取 TRUE 和 FALSE 各一次,同时要求“b>0”取 TRUE 和 FALSE 各一次。
高的代码覆盖率不一定能保证软件的质量,但是低的代码覆盖率一定不能能保证软件的质量。
代码覆盖率工具
JaCoCo,一款 Java 代码的主流开源覆盖率工具,可以很方便地嵌入到 Ant、Maven 中,并且和很多主流的持续集成工具以及代码静态检查工具,比如 Jekins 和 Sonar 等。
代码覆盖率工具的实现原理
最基本的方法就是注入,在被测代码中自动插入用于覆盖率统计的探针(Probe)代码,并保证插入的探针代码不会给原代码带来任何影响。
字节码注入可分为On-The-Fly 注入模式和Offline 注入模式。
主流的 JaCoCo采用的java Agent方式。
07 如何高效填写软件测试缺陷报告?
缺陷报告本身的质量将直接关系到缺陷被修复的速度以及开发工程师的效率,同时还会影响到测试工程师的信用、测试与开发人员协作的有效性。
一份高效的软件缺陷报告,应该包括缺陷标题、缺陷概述、缺陷影响、环境配置、前置条件、缺陷重现步骤、期望结果和实际结果、优先级和严重程度、变通方案、缺陷发现版本、出现频率、根原因分析,以及附件这几大部分。
08 以终为始,如何才能做好测试计划?
测试计划包括:测试范围、测试策略、测试资源、测试进度和测试风险预估
1)测试范围:被测对象以及主要的测试内容,明确“测什么”和“不测什么”
2)测试策略:明确“先测什么后测什么”和“如何来测”,还需要说明,采用什么样的测试类型和测试方法
功能测试
兼容性测试
性能测试
易用性测试
UI测试
安全测试
3)测试资源:通常包括测试人员和测试环境。明确“谁来测”和“在哪里测”这两个问题。
4)测试进度:描述各类测试的开始时间,所需工作量,预计完成时间, 并以此为依据来建议最终产品的上线发布时间。
5)测试风险预估:通常需求变更、开发延期、发现重大缺陷和人员变动是引入项目测试风险的主要原因。
备注:
1)兼容性测试
如何确定需要覆盖的移动设备类型以及移动端版本列表?
如果是既有产品,你可以通过大数据技术分析产品的历史数据得出 Top 30% 的移动设备以及 iOS/Android 的版本列表,那么兼容性测试只需覆盖这部分即可。如果是一个全新的产品,你可以通过 TalkingData 这样的网站来查看目前主流的移动设备,分辨率大小、iOS/Android 版本等信息来确定测试范围。
2)性能测试
需要在明确了性能需求(并发用户数、响应时间、事务吞吐量等)的前提下,结合被测系统的特点,设计性能测试场景并确定性能测试框架。eg,是直接在 API 级别发起压力测试,还是必须模拟终端用户行为进行基于协议的压力测试。再比如,是基于模块进行压力测试,还是发起全链路压测。
09 软件测试工程师的核心竞争力是什么?
测试工程师核心竞争力
- 测试策略设计能力
- 测试用例设计能力
- 快速学习能力
- 探索性测试思维
- 缺陷分析能力
- 自动化测试技术
- 良好的沟通能力
测试策略设计能力是指,对于各种不同的被测软件,能够快速准确地理解需求,并在有限的时间和资源下,明确测试重点以及最适合的测试方法的能力。
具备出色的测试策略设计能力,你可以非常明确地回答出测试过程中遇到的这些关键问题:
- 测试要具体执行到什么程度;
- 测试需要借助于什么工具;
- 如何运用自动化测试以及自动化测试框架,以及如何选型;
- 测试人员资源如何合理分配;
- 测试进度如何安排;
- 测试风险如何应对
学习能力,当你学习一个新的开源工具时,建议你直接看官方文档:一来,这里的内容是最新而且是最权威的;二来,可以避免网上信息质量的参差不齐。知识输入源头是单一,而且权威的话,你的学习曲线也必然会比较平滑。
缺陷分析能力,通常包含三个层面的含义:
1)对于已经发现的缺陷,结合发生错误的上下文以及后台日志,可以预测或者定位缺陷的发生原因,甚至可以明确指出具体出错的代码行,由此可以大幅缩短缺陷的修复周期,并提高开发工程师对于测试工程师的认可以及信任度;
2)根据已经发现的缺陷,结合探索性测试思维,推断同类缺陷存在的可能性,并由此找出所有相关的潜在缺陷;
3)可以对一段时间内所发生的缺陷类型和趋势进行合理分析,由点到面预估整体质量的健康状态,并能够对高频缺陷类型提供系统性的发现和预防措施,并以此来调整后续的测试策略。
自动化测试的核心价值还是“测试”本身,“自动化”仅仅是手段,实际工作中千万不要本末倒置,把大量的精力放在“自动化”上,一味追求自动化而把本质的“测试”弱化了。
沟通能力会直接影响事务开展的效率。良好清晰的沟通能力,是一个技术优秀的测试工程师能否获得更大发展的“敲门砖”,也是资深测试工程师或者测试主管的核心竞争力。
测试开发工程师的核心竞争力
首先既然是测试开发工程师,那么代码开发能力是最基本的要求。可以说,一个合格的测试开发工程师一定可以成为一个合格的开发工程师,但是一个合格的开发工程师不一定可以成为合格的测试开发工程师
- 测试系统需求分析能力
- 更宽广的知识体系
除了代码开发能力,测试开发工程师更要具备测试系统需求分析的能力。你要能够站在测试架构师的高度,识别出测试基础架构的需求和提高效率的应用场景。从这个角度说,你更像个产品经理,只不过你这个产品是为了软件测试服务的。
测试开发工程师需要具备非常宽广的知识体系,你不仅需要和传统的测试开发工程师打交道,因为他们是你构建的测试工具或者平台的用户;而且还要和 CI/CD、和运维工程师们有紧密的联系,因为你构建的测试工具或者平台,需要接入到 CI/CD 的流水线以及运维的监控系统中去。
除此之外,你还要了解更高级别的测试架构部署和生产架构部署、你还必须对开发采用的各种技术非常熟悉。可见,对于测试开发工程师的核心竞争力要求是非常高的,这也就是为什么现今市场上资深的测试开发工程师的价格会高于资深的开发工程师的原因。
10 软件测试工程师需要掌握的非测试知识有哪些?
小到Linux/Unix/Windows操作系统的基础知识,Oracle/MySQL等传统关系型数据库技术,NoSQL非关系型数据库技术,中间件技术,Shell/Python脚本开发,版本管理工具与策略,CI/cd流水线设计,F5负载均衡技术,Fiddler/Wireshark/Tcpdump等抓包工具,浏览器Developer Tool等;
大到网站架构设计,容器技术,微服务架构,服务网格(Service Mesh),DevOps,云计算,大数据,人工智能和区块链技术等。
DevOps思维
DevOps 的具体表现形式可以是工具、方法和流水线,但其更深层次的内涵还是在思想方法,以敏捷和精益为核心,通过发现问题,以系统性的方法或者工具来解决问题,从而实现持续改进。
对于 DevOps,可以从深入掌握 Jenkins 之类的工具开始,到熟练应用和组合各种 plugin 来完成灵活高效的流水线搭建,之后再将更多的工具逐渐集成到流水线中以完成更多的任务。
11 互联网产品的测试策略应该如何设计?
传统软件产品的测试策略(金字塔测试策略)
1)单元测试
金字塔最底部是单元测试,属于白盒测试的范畴,通常由开发工程师自己完成。每个版本build过程中会反复执行单元测试。
2)API测试
金字塔中间部分是 API 测试,主要针对的是各模块暴露的接口,通常采用灰盒测试方法。灰盒测试方法是介于白盒测试和黑盒测试之间的一种测试技术,其核心思想是利用测试执行的代码覆盖率来指导测试用例的设计。
3)GUI测试
GUI 测试,也称为端到端(E2E,End-to-end)测试。模拟用户在软件界面上的各种操作,并验证这些操作对应的结果是否正确。缺点是用例维护的代价大。
互联网产品的测试策略设计(菱形测试策略)
互联网产品的 GUI 测试通常采用“手工为主,自动化为辅”的测试策略,手工测试往往利用探索性测试思想,针对新开发或者新修改的界面功能进行测试,而自动化测试的关注点主要放在相对稳定且核心业务的基本功能验证上。
互联网的测试重点在API上,API 测试可以实现良好的投入产出比。
互联网产品通常会分为应用层和后端服务,后端服务又可以进一步细分为应用服务和基础服务。互联网产品的全面单元测试只会应用在那些相对稳定和最核心的模块和服务上,而应用层或者上层业务服务很少会大规模开展单元测试。
菱形测试策略的关键点
- 以中间层的 API 测试为重点做全面的测试。
- 轻量级的 GUI 测试,只覆盖最核心直接影响主营业务流程的 E2E 场景。
- 最上层的 GUI 测试通常利用探索式测试思维,以人工测试的方式发现尽可能多的潜在问题。
- 单元测试采用“分而治之”的思想,只对那些相对稳定并且核心的服务和模块开展全面的单元测试,而应用层或者上层业务只会做少量的单元测试。
GUI自动化测试篇(10讲)
12 从0到1:你的第一个GUI自动化测试
selenium 2.0 工作原理
Selenium WebDriver 是典型的 Server-Client 模式,Server 端就是 Remote Server。以下是 Selenium 2.0 工作原理的解析。
1、当使用 Selenium2.0 启动浏览器 Web Browser 时,后台会同时启动基于 WebDriver Wire 协议的 Web Service 作为 Selenium 的 Remote Server,并将其与浏览器绑定。绑定完成后,Remote Server 就开始监听 Client 端的操作请求。
2、执行测试时,测试用例会作为 Client 端,将需要执行的页面操作请求以 Http Request 的方式发送给 Remote Server。该 HTTP Request 的 body,是以 WebDriver Wire 协议规定的 JSON 格式来描述需要浏览器执行的具体操作。
3、Remote Server 接收到请求后,会对请求进行解析,并将解析结果发给 WebDriver,由 WebDriver 实际执行浏览器的操作。
4、WebDriver 可以看做是直接操作浏览器的原生组件(Native Component),所以搭建测试环境时,通常都需要先下载浏览器对应的 WebDriver。
13 效率为王:脚本与数据的解耦 + Page Object模型
测试脚本和数据解耦
“测试脚本和数据解耦”的本质是实现了数据驱动的测试,让操作相同但是数据不同的测试可以通过同一套自动化测试脚本来实现,只是在每次测试执行时提供不同的测试输入数据。
POM(页面对象模型)封装思路
(1)POM模式将页面分成三层
- 表现层(封装公共操作在base类)
页面中可见的元素,都属于表现层。(元素定位器的编写) - 操作层
对页面可见元素的操作。点击、输入、拖拽等。 - 业务层
在页面中对若干元素操作后所实现的功能。(就是测试用例)
(2)POM模式的核心要素(重点)
- 在POM模式中将公共方法统一封装成到一个BasePage 类中,换句话说该基类对Selenium的常用操作做二次封装。
- 每个页面对应一个page类,page类都需要继承 BasePage,通过 driver 来管理本page类中的元素,并将page类中的操作封装成一个个的方法。换句话说,就是page类中封装页面表现层和操作层。
TestCase
继承unittest.Testcase
类,并且依赖 page 类,从而实现相应的测试步骤。
1)把一些公共的方法放到此类中,这个类将被PO对象继承
"""
封装公共方法
"""
from selenium import webdriver
import time
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class Base:
def __init__(self, browser="chrome"):
"""
初始化driver
:param browser:浏览器名称
"""
if browser == "chrome":
self.driver = webdriver.Chrome()
elif browser == "firefox":
self.driver = webdriver.Firefox()
elif browser == "ie":
self.driver = webdriver.Ie()
else:
self.driver = None
print("请输入正确的浏览器,例如:chrome,firefox,ie")
def open_url(self, url):
"""
打开地址
:param url: 被测地址
:return:
"""
self.driver.get(url)
time.sleep(2)
def find_element(self, locator, timeout=10):
"""
定位单个元素,如果定位成功返回元素本身,如果失败,返回False
:param locator: 定位器,例如("id","id属性值")
:return: 元素本身
"""
try:
element = WebDriverWait(self.driver, timeout).until(EC.presence_of_element_located(locator))
return element
except:
print(f"{locator}元素没找到")
return False
def click(self, locator):
"""
点击元素
:return:
"""
element = self.find_element(locator)
element.click()
def send_keys(self, locator, text):
"""
元素输入
:param locator: 定位器
:param text: 输入内容
:return:
"""
element = self.find_element(locator)
element.clear()
element.send_keys(text)
def close(self):
"""
关闭浏览器
:return:
"""
time.sleep(2)
self.driver.quit()
if __name__ == '__main__':
base = Base()
base.open_url("https://sso.kuaidi100.com/sso/authorize.do")
base.close()
2)每个页面对应一个page类
"""
定位元素的定位器和操作元素方法分离开,元素定位器全部放一起,然后每一个操作元素动作写成一个方法。
管理登陆页面所有的元素,
以及操作这些元素所用的方法。
"""
from common.base import Base
class LoginPage(Base):
# 编写定位器和页面属性
name_input_locator = ("id", "name")
passwd_input_locator = ("id", "password")
submit_button_locator = ("id", "submit")
username = 'xxxxxxxxxxx'
userpasswd = 'xxxxxx'
url = 'https://sso.kuaidi100.com/sso/authorize.do'
# """封装元素操作"""
# 输入用户名
def name_imput(self):
self.send_keys(self.name_input_locator, self.username)
# 输入密码
def passwd_imput(self):
self.send_keys(self.passwd_input_locator, self.userpasswd)
# 点击登陆
def click_submit(self):
self.click(self.submit_button_locator)
if __name__ == '__main__':
base = Base('firefox')
base.open_url(url=LoginPage.url)
3)测试方法及测试类
# 1. 导入包
import unittest
from pages.login_page import LoginPage
# 定义测试类
class TestCaseLogin(unittest.TestCase):
def setUp(self) -> None:
self.driver = LoginPage()
self.driver.open_url(LoginPage.url)
def tearDown(self) -> None:
# 5. 关闭浏览器
self.driver.close()
#todo:添加数据驱动装饰器
def testLogin(self):
"""登陆测试用例"""
self.driver.name_imput()
self.driver.passwd_imput()
self.driver.click_submit()
if __name__ == '__main__':
unittest.main()
14 更接近业务的抽象:让自动化测试脚本更好地描述业务
如何衔接两个操作函数之间的页面?
完成一个业务流程操作,往往会需要依次调用多个操作函数,但是操作函数和操作函数之间会有页面衔接的问题,即前序操作函数完成后的最后一个页面,必须是后续操作函数的第一个页面。如果连续的两个操作函数之间无法用页面衔接,那就需要在两个操作函数之间加入额外的页面跳转代码,或者是在操作函数内部加入特定的页面跳转代码。
业务流程抽象
业务流程抽象是,基于操作函数的更接近于实际业务的更高层次的抽象方式。基于业务流程抽象实现的测试用例往往具有较好的灵活性,可以根据实际测试需求方便地组装出各种测试用例。
业务流程的核心思想是,从业务的维度来指导测试业务流程的封装。由于业务流程封装通常很贴近实际业务,所以特别适用于组装面向终端用户的端到端(E2E)的系统功能测试用例,尤其适用于业务功能非常多,并且存在各种组合的 E2E 测试场景。