Docker多阶段构建:高效QA自动化测试的技巧
gudong366 2025-07-04 20:26 6 浏览
每个 QA 工程师都知道缓慢的测试环境和臃肿的容器带来的挫败感,这些容器构建和部署需要花费大量时间。
如果你能够将测试镜像大小减少 70%,并且在不牺牲测试质量的情况下加快 CI 流水线的速度,那会怎样?Docker 多阶段构建就是这样一个功能,自 Docker 17.05 以来就已存在,但在 QA 自动化工作流程中仍然令人惊讶地被低估。
传统测试容器的问题
传统的基于 Docker 的测试环境通常因为包含以下内容而变得不必要地庞大:
- 在运行时不需要的构建工具和依赖项;
- 源代码和中间构建产物;
- 开发库和头文件;
- 文档和其他非必要文件。
这种臃肿带来了几个问题:
- 镜像构建速度变慢;
- 在 CI/CD 流水线中增加网络传输时间;
- 存储成本增加;
- 容器启动时间变长;
- 在测试执行期间使用更多资源。
多阶段构建登场
多阶段构建允许我们在 Dockerfile 中使用多个 FROM 语句。
每个 FROM 指令可以使用不同的基础镜像,并开始构建的一个新阶段。我们可以选择性地将一个阶段中的工件复制到另一个阶段,留下我们不需要的所有内容。
这对于 QA 自动化来说是完美的,因为我们可以:
- 在一个阶段构建我们的测试依赖项;
- 在另一个阶段编译我们的测试工具;
- 创建一个最终的最小运行时镜像,其中只包含运行测试所需的内容。
实际示例
让我们为一个基于 Python 的 Web 应用程序测试环境创建一个多阶段 Dockerfile,该环境使用 Selenium WebDriver 和 Chrome。
第一阶段:构建依赖项
第一阶段使用完整的 Python 镜像作为基础。我们使用 AS builder 为这个阶段命名,这允许我们在 Dockerfile 的后续部分引用它。我们在 /build 处设置工作目录,并将 requirements.txt 文件(而不是整个项目)复制到容器中。
然后,我们使用 pip 的 wheel 命令将所有 Python 依赖项预先构建为 wheel 文件,这些文件存储在 /build/wheels 目录中。
--no-cache-dir 和 --no-deps 标志有助于创建最小的 wheel 包。这个阶段的唯一目的是构建一次 Python 包,因此我们不需要在后续阶段重新构建它们。
FROM python:3.11 AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /build/wheels -r requirements.txt
第二阶段:Chrome 和驱动程序准备
第二阶段从一个精简的 Debian 镜像开始,并命名为 "chrome"。在这里,我们安装下载和安装 Chrome 所需的最小实用工具。
这个过程从安装基本工具开始:wget 用于下载文件,gnupg 用于加密操作,ca-certificates 用于安全连接,unzip 用于解压压缩文件。我们使用 --no-install-recommends 标志来防止安装不必要的包。
在安装这些先决条件之后,我们下载谷歌的官方签名密钥,并使用 GPG 处理它以创建一个适当的密钥环文件。
这遵循现代 Debian 安全实践,将密钥存储在 /usr/share/keyrings/ 中,而不是使用已弃用的 apt-key 命令。然后,我们使用 sources 列表中的 signed-by 属性配置软件包仓库,该属性引用了这个密钥环文件。
接下来,我们更新软件包列表,并再次使用 --no-install-recommends 标志安装 Chrome,以保持安装的最小化。
最后,我们通过删除 apt 软件包列表目录来清理,这通过删除安装后不再需要的缓存软件包元数据来减小镜像大小。
整个过程使用 && 运算符链接在一起,创建一个单一的 Docker 层,这比使用单独的 RUN 命令更高效。
FROM debian:bullseye-slim AS chrome
RUN apt-get update && apt-get install -y \
wget \
gnupg \
ca-certificates \
unzip \
--no-install-recommends && \
wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor > /usr/share/keyrings/google-linux-signing-key.gpg && \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-linux-signing-key.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && \
apt-get update && \
apt-get install -y google-chrome-stable --no-install-recommends && \
rm -rf /var/lib/apt/lists/*
在接下来的部分中,我们通过查询 LATEST_RELEASE 端点来确定最新兼容的 ChromeDriver 版本。我们动态地捕获这个版本号到 CHROME_DRIVER_VERSION 变量中。
然后,我们安静地下载对应的 ChromeDriver zip 文件,并将其保存到临时目录中。解压到 /usr/bin/ 之后,我们删除 zip 文件以节省空间,并使用 chmod 命令使 ChromeDriver 可执行。
这种方法确保我们拥有一对兼容的 Chrome 和 ChromeDriver,而不会硬编码可能会过时的版本号。
RUN CHROME_DRIVER_VERSION=$(wget -qO- https://chromedriver.storage.googleapis.com/LATEST_RELEASE) \
&& wget -q --no-verbose -O /tmp/chromedriver.zip https://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip \
&& unzip /tmp/chromedriver.zip -d /usr/bin/ \
&& rm /tmp/chromedriver.zip \
&& chmod +x /usr/bin/chromedriver
第三阶段:最终精简测试镜像
现在,我们从一个精简的 Python 镜像开始最终阶段。我们在 /app 处创建一个工作目录,并使用 COPY --from=chrome 有选择地从上一个阶段拉取必要的 Chrome 文件。
这就是多阶段构建真正发光的地方:我们不会带上安装 Chrome 所使用的所有构建工具和依赖项。这种方法允许我们获得 Chrome 和 ChromeDriver,而不会带来它们安装过程中的所有包袱。
FROM python:3.11-slim
WORKDIR /app
COPY --from=chrome /opt/google/chrome /opt/google/chrome
COPY --from=chrome /usr/bin/chromedriver /usr/bin/chromedriver
COPY --from=chrome /usr/bin/google-chrome-stable /usr/bin/google-chrome-stable
接下来,我们只安装 Chrome 在运行时需要的最小运行时库。这些是共享库,Chrome 在运行时需要这些库来处理各种系统级操作,如渲染和输入处理。
我们使用 rm -rf /var/lib/apt/lists/\* 清理 apt 的缓存,以减小镜像大小。再次,我们使用 --no-install-recommends 来保持简洁。这种对依赖项的专注方法比简单地在最终镜像中安装 Chrome 要高效得多,因为我们会得到许多在我们的测试环境中不需要的不必要的包。
RUN apt-get update && apt-get install -y \
libglib2.0-0 \
libnss3 \
libx11-6 \
libx11-xcb1 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxrandr2 \
libxrender1 \
libxss1 \
libxtst6 \
fonts-liberation \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
之后,我们从第一阶段的 "builder" 复制预先构建的 Python 轮子和 requirements.txt 文件(用于参考)。我们使用 pip 安装轮子,这比从源代码构建包要快得多。然后,我们在安装后通过删除轮子和 requirements.txt 来清理。这种方法让我们在不需要编译器或开发头文件的情况下安装 Python 包,这显著减小了镜像大小并提高了构建时间。
COPY --from=builder /build/wheels /wheels
COPY requirements.txt .
RUN pip install --no-cache /wheels/* \
&& rm -rf /wheels \
&& rm requirements.txt
最后,我们只复制测试文件和 pytest 配置,而不是整个项目代码库。这种选择性复制进一步减小了镜像大小,并使其专注于测试。
我们设置环境变量以在无头模式下配置 Chrome,并使用适当的 Docker 设置使其能够在没有显示或沙箱要求的情况下运行。
我们设置默认命令以在我们的测试目录上运行 pytest 并输出详细信息。这种最终配置保持对测试的关注,并消除了任何对测试执行不必要的代码或文件。
COPY tests/ /app/tests/
COPY conftest.py /app/
ENV CHROME_OPTIONS="--headless --no-sandbox --disable-dev-shm-usage"
CMD ["pytest", "tests/", "-v"]
好处与实施策略
多阶段构建方法改变了我们为 QA 自动化创建 Docker 容器的方式。我们的测试现在在大约比传统单阶段构建小 70% 的镜像中运行,显著减少了存储需求和网络传输时间。这种大小减少直接转化为 CI/CD 流水线中更快的启动时间,允许团队更快地获得反馈并更快速地迭代。
随着我们从最终镜像中移除不必要的构建工具和包,安全性显著提高。每个被移除的组件都代表着一个潜在的漏洞,创建了一个更安全的测试环境,攻击面显著减少。
精简的容器还为测试提供了一个更干净、更接近生产的环境,最小化了困扰测试工作的“在我的机器上可以工作”的问题。
Docker 的智能缓存机制与多阶段构建配合得非常好。每个阶段都可以独立缓存,这意味着对测试代码的更改不会触发依赖项或浏览器安装的耗时重建。当我们修改测试时,只有最终阶段需要重建,节省了宝贵的开发时间。
为了最大化这些好处,我们应该有条理地组织我们的测试代码。让我们将测试与测试工具和支持文件分开,以便更有效地将它们复制到最终镜像中。
我们应该始终使用基础镜像的具体版本,而不是 “latest” 标签,以确保在不同环境和时间中可重复构建。这种版本控制策略防止了意外更改破坏我们的测试基础设施。
我们应该花时间分析哪些工件实际上需要在运行时存在。通常,源代码或中间构建产品对于测试执行来说不是必要的,可以被留下。我们应该创建一个全面的 .dockerignore 文件,以防止在构建过程中复制不必要的文件,进一步精简我们的镜像。
对于高级场景,我们可以利用构建参数来创建参数化构建,以满足不同的测试环境需求,允许一个 Dockerfile 根据需要生成不同浏览器或测试配置的容器。
结论
多阶段 Docker 构建可能不是最新的功能,但它们无疑是QA自动化武器库中一个被低估的秘密武器。通过有条理地分离构建和测试关注点,我们可以创建更精简、更快、更可靠的测试环境,这将使我们的开发人员和财务部门都感到满意。
完整的Dockerfile可在我们的GitHub页面上轻松访问和实验,允许你立即在自己的QA自动化项目中实施这种高效的多阶段构建方法。试试吧!
可以到我的个人号:atstudy-js
这里有一起交流行业热点和offer机会,可加入↓↓↓↓↓↓
行业测试涨薪交流群,内含银行业务、车载、AI测试、互联网、游戏更多行业测试实战和面试题库 &【AI智能体】等各种好用的
助你快速转行&进阶测试开发技术,稳住当前职位同时走向高薪之路
相关推荐
- linux sed系列 第四篇:sed工业实战——日志处理与数据清洗
-
“掌握了sed的编程能力后,我们如同装备精良的工匠,终于可以踏入真实的工业战场。本篇将聚焦sed在日志分析、数据合规化、多文件批处理等场景中的应用,看它如何在海量数据中游刃有余,展现文本处理的...
- Linux下sed的简单使用(linux中sed是什么意思)
-
1、sed简介stremeditor流编辑器,它是一项Linux指令,功能同awk类似,差别在于,sed简单,对列处理的功能要差一些,awk的功能复杂,对列处理的功能比较强大,sed编辑器是一行一...
- linux基础命令之date命令(linux中的date)
-
date命令主要用于显示或者设置系统时间语法格式:date参数对象使用date命令时,最好先使用date--help命令查看支持哪些参数,有些小型Linux系统下的date命令,只支持一些基本参...
- Ubuntu linux 常用命令(ubuntu常用的50个命令)
-
使用dpkg命令来安装.deb包。sudodpkg-i~/example.deb如果在安装过程中遇到依赖问题,可以使用以下命令来修复:sudoapt-getinstall-f将flut...
- Linux基础命令-sed命令(linux教程:sed命令的用法)
-
Sed全名streameditor流编辑器,它是一个强大的文本处理工具,它可以从文件中接受输入,也可以接受来自标准输入流的输入,它擅长取行。Sed的用途非常广泛,包括:1)文本替换2)选择性的输...
- linux sed系列 第二篇:sed进阶技巧——地址定位与正则表达式
-
“上一篇我们掌握了sed的基础替换,如同获得了第一把钥匙。现在,让我们更进一步,学习如何精准锁定目标行,如同拥有了导航地图,让每一次操作都直击要害!”地址定位的四种维度sed的强大,很大程度上源...
- 火狐Firefox浏览器140发布:手动Unload标签页、优化翻译体验等
-
IT之家6月24日消息,Mozilla在发布版本139不到一个月后,推出了最新的开源网页浏览器Firefox140。新版本增加了手动Unload标签页的功能,优化了垂直标签页的调...
- Linux 基本正则表达式及扩展正则表达式功能举例
-
在Linux中,正则表达式(RegularExpression)是一种强大的模式匹配工具,用于在文本中查找、匹配和处理特定模式的字符串。Linux支持两种类型的正则表达式:基本正则表达式(Basic...
- linux下find命令的经典26个使用示例
-
简介find命令是基于unix的操作系统中常用的工具之一。顾名思义,它在目录层次结构中查找文件和目录。用户可以传递不同的参数,并根据文件的名称、扩展名、类型、大小、权限、修改时间、所有者、组等搜索文件...
- linux运维中特殊符号的应用与实践
-
路径位置类的特殊符号(1)、波浪线(~)在linux系统的命令行中,~表示用户的家目录,超级用户为/root,普通用户为/home。假设我当前目录在usr/local下[root@xrylocal]...
- 开源框架log4cpp实战(开源gui框架)
-
1.Log4cpp使用Log4cpp中主要包含Category(种类),Appender(附加器),Layout(布局),Priorty(优先级),NDC(嵌套的诊断上下文)。Category、App...
- Linux find命令详解(linux find -l)
-
一、命令介绍Linuxfind命令是类unix操作系统中最重要和最常用的命令行实用程序之一。find命令用于根据指定的条件搜索和定位与参数匹配的文件和目录列表。find命令提供了广泛的选项,允许用户...
- Linux运维:单引号与双引号的使用(linux 单引号和双引号)
-
1、单引号的使用单引号可以将它中间的所有任意字符还原为字面意义,实现屏蔽Shell元字符的功能。注意不可以在两个单引号中间单独插入一个单引号,单引号必须成对出现。示例1:定义一个变量,并输出变量的...
- Linux技巧:find 命令用法详细说明,看完会有收获
-
在Linux命令中,find是比较复杂难用的命令。使用该命令搜索文件时,常常发现自己找了一些例子能用,但稍微改一下条件,就搜不到想要的结果。下面会以一些实例来说明使用find命令的关键要点和...
- Linux Shell中单引号、双引号、反引号的解释
-
1、单引号('')单引号所见即所得,直接显示单引号里的内容。即单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的。比如下面的例子,单引号所见即所得。2、双引号("...
- 一周热门
- 最近发表
-
- linux sed系列 第四篇:sed工业实战——日志处理与数据清洗
- Linux下sed的简单使用(linux中sed是什么意思)
- linux基础命令之date命令(linux中的date)
- Ubuntu linux 常用命令(ubuntu常用的50个命令)
- Linux基础命令-sed命令(linux教程:sed命令的用法)
- linux sed系列 第二篇:sed进阶技巧——地址定位与正则表达式
- 火狐Firefox浏览器140发布:手动Unload标签页、优化翻译体验等
- Linux 基本正则表达式及扩展正则表达式功能举例
- linux下find命令的经典26个使用示例
- linux运维中特殊符号的应用与实践
- 标签列表
-
- linux一键安装 (31)
- linux运行java (33)
- ln linux (27)
- linux 磁盘管理 (31)
- linux 内核升级 (30)
- linux 运行python (28)
- linux 备份文件 (30)
- linux 网络测试 (30)
- linux 网关配置 (31)
- linux jre (32)
- linux 杀毒软件 (32)
- linux语法 (33)
- linux博客 (33)
- linux 压缩目录 (37)
- linux 查看任务 (32)
- 制作linux启动u盘 (35)
- linux 查看存储 (29)
- linux乌班图 (31)
- linux挂载镜像 (31)
- linux 软件源 (28)
- linux题目 (30)
- linux 定时脚本 (30)
- linux 网站搭建 (28)
- linux 远程控制 (34)
- linux bind (31)