软件测试的格局正在发生变化。在竞争激烈的技术世界中,速度和质量通常被视为对立的力量。我们被告知,如果我们要在竞争对手击败我们之前成功地将我们的产品送到用户手中,就必须“快速行动并打破常规”。这通常意味着以推出新功能的名义牺牲质量和信心。
虽然这种权衡有时是有意义的,但它不可避免地会以技术缺陷、错误、用户信心下降以及产品和工程团队的阻碍因素的形式困扰您。随着这些团队的扩展,我们的目标是越来越快地发货。与此同时,我们构建的框架和技术变得更加动态。传统的测试自动化方法不仅不足,而且正在成为一种负担而不是资产。
测试架构
为了了解Rainforest QA公司测试策略中的位置及其带来的巨大好处,我们首先需要了解测试架构的基础知识。
有许多层可以正确测试应用程序及其与之交互的许多服务。为简单起见,我们将用一个过于简化的模型来表示一般的测试架构:测试金字塔。
该模型最初来自Mike Cohn的《Successing with Agile》一书,由三层组成:
单元测试
服务测试(又名集成测试)
用户界面测试
此模型不能充分捕获测试现代应用程序的多层,但心智模型是概括和理解测试概念的理想选择。
在金字塔的底部,测试非常精细,并且以非常低的成本(基本上免费)快速执行。随着金字塔的向上移动,测试变得更加通用、更慢、更昂贵(在执行和创建/维护方面)。
对于我们的理论应用,请考虑以下技术堆栈:
在我们的示例中,前端(用户界面)是一个与我们的后端 API 对话的 React 应用程序。该 API
负责读取/写入数据库、与我们的其他微服务通信以及与外部第三方 API 交互。
第 1 层:单元测试
金字塔的底层是抵御虫子的第一道防线。它用于测试我们技术堆栈的特定部分。特别是,它测试每个应用程序/服务的“单元”,这是一个任意定义的代码“块”。您的工程团队需要围绕定义“单元”以及如何测试它们做出许多细微的决定,但这超出了本文的范围。
我们技术堆栈中的每个红框都使用自己的一组单元测试进行测试。
单元测试在完全隔离和人工/模拟环境中执行。这意味着 React 单元测试对我们的后端 API 一无所知,反之亦然。我们正在测试每个应用程序的内部部件,并确保各个部件按预期工作。这方面的一个例子是测试单个
React 组件 - 对于我们的示例,我们将使用一个简单的按钮。
const
MyButton = ({ onClick, text }) => (
{text}
); |
我们的单元测试将测试两件事:
按钮呈现为提供给它的文本
单击按钮时,它会调用 onClick 提供的函数
使用 Jest + Enzyme(这是用于测试 React 应用程序的标准库),我们的单元测试如下所示:
describe('Button',
() => {
beforeEach(() => {
props = {
text: 'hello world',
onClick: jest.fn()
};
component = mount();
});
it('renders the text', () => {
expect(component.text()).toEqual('hello world');
});
it('invokes onClick prop when clicked', ()
=> {
component.simulate('click');
expect(props.onClick).toHaveBeenCalled();
});
}); |
太棒了,测试通过了,我们现在确信我们的 React 代码可以工作。这是测试简单组件的标准方法,这没有任何问题,但有重要事项需要注意:
此测试在完全隔离的情况下运行 - 它没有关于它将在应用程序中使用的位置的上下文。
它天真地假设提供给它的 onClick 函数是有效的。通过“有效”,我的意思是onClick值实际上是一个函数(我可以很容易地意外提供一个数字),并且该函数没有充满错误。简而言之,它假设工程师将正确使用MyButton。
我们正在编写代码来测试代码。
我们可以通过静态类型检查等解决方案来降低 #1 的风险,但关注 #2 本质上容易受到错误和误报的影响。是否应该编写代码来测试测试代码的代码?每次编写单元测试时,我们都会假设我们的测试代码是有效的
- 这并不总是正确的。
不过,这不是一个大问题 - 请记住,这只是第 1 层。我们不能指望第 1 层捕获所有错误。如果我们把金字塔向上翻转,把它想象成一个漏斗,我们可以看到,当我们推动代码通过层时,可能的错误数量是如何减少的。
现在我们相对有信心我们的代码在粒度级别上工作,我们可以进入测试金字塔的下一层。
第 2 层:服务/集成测试
术语“服务”和“集成”是可以互换的,而且它们都相当模糊。与我们在单元测试中对“单元”的模糊定义类似,我们的“集成测试”位于应用程序的边界,并测试其与外部服务(如数据库和第三方
API)的交互。
在我们的可视化中,我们正在测试应用程序/服务之间的红色连接。
集成测试不能像我们的单元测试那样在完全隔离的模拟环境中运行。它们需要设置一些测试基础结构,以便我们可以测试服务之间的交互。
例如,我们要测试后端 API 是否与我们的数据库正确交互。其中一个测试的执行可能如下所示:
启动数据库和 API
将 API 连接到数据库
向需要它写入数据库的 API 创建 HTTP
从数据库中读取以确保已写入预期数据
我们不仅测试 API <=> DB 之间的交互,而且这也可以看作是我们单元测试的超集。如果我们的集成测试通过,则可以合理地假设
API 的代码按预期工作(这就是我们的单元测试的目的)。我们免费获得冗余!</=>
我们可以通过类似的方式测试我们与第三方服务的集成:
启动接口
将 API 连接到第三方服务
在我们的 API 中触发一个函数,该函数要求它从第三方服务读取
检查我们的 API 是否正确处理响应
集成测试中需要注意的重要事项:
我们不再完全隔离地运行测试。我们需要设置一个测试环境(生产应用程序的功能克隆,减去一些数据和扩展功能),这具有测试运行应用程序的基础结构的额外好处。此环境非常适合尽可能接近我们的生产环境。
尽管我们不再完全孤立,但我们仍然隔离了技术堆栈的特定部分。当多个服务相互交互时,可能会出现错误。从本质上讲,我们只是在测试我们技术堆栈的更大“单元”。我们必须控制系统的状态,这显然是执行测试所必需的,但不一定模拟来自用户的“真实世界”输入。
我们仍在编写代码来测试我们的代码。我们的测试代码可能存在错误。
我们的集成层有许多不同的子层,随着您的技术堆栈的增长,这些子层会变得更加复杂。当您有许多团队并行工作时,复杂的微服务架构可能很棒,但您需要确保集成不会中断。这些风险可以通过合同测试等方法得到缓解。
第 3 层:UI 测试
最后一层允许我们在“真实世界”场景中测试我们的应用程序。我们不再测试单个部分 - 相反,我们正在从最终用户的角度测试我们的整个技术堆栈。
UI测试的行业标准方法是使用Selenium编写自动化脚本。还有其他技术和解决方案,但它们最终采用相同的方法
- 基于 DOM 的测试。一个例外是使用人工手动执行 QA,但由于显而易见的原因,它不像自动化方法那样可扩展。
我们不同的应用程序/服务相互交互,但我们的用户最终通过一个入口点与我们的服务进行交互:用户界面。此交互显示为下面的红色箭头。
由于正确测试交互的要求,我们被迫隐式测试我们的整个技术堆栈:
需要完整的测试环境,这意味着运行我们的每个应用程序/服务。
测试必须在实际的 Web 浏览器(或桌面应用程序、移动应用程序等的等效环境中运行)。
测试只能通过与 UI 交互(就像真实用户一样)并检查 UI 是否正确更新来执行。
目前还不清楚为什么这会隐含地测试我们的整个技术堆栈。考虑单击UI中的按钮时会发生什么情况:
用户单击按钮
执行 React/Javascript 代码 - 向 API 发送 HTTP 请求,并禁用按钮以防止用户发送垃圾邮件点击
API 接收请求、对请求进行身份验证、从其他服务请求数据、读取/写入数据库并响应请求
反应应用收到响应
解析数据并对其执行某些操作(重新启用按钮、显示通知、导航到其他页面、更新 UI 的其他部分等)
在第1层和第2层中,我们分别测试了这些“单元”中的每一个。尽管这只是一个简单的按钮点击,但我们已经测试了我们的技术堆栈的许多部分可以相互协调。
第 3 层的行业标准方法是使用 Selenium 和 Cypress 等工具进行基于 DOM 的测试。在我们的下一篇博客文章中,我们将研究基于
DOM 的测试的衰落,以及雨林自动化如何成为 UI 测试的未来。 |