4. 开发环境搭建:基于Docker的嵌入式测试环境构建

做嵌入式单元测试,最头疼的问题是什么?

环境不一致。你在Ubuntu 18.04上编译通过,同事的20.04就报错。更别说不同芯片厂商的工具链版本冲突了。

我几年前带一个项目时,光帮团队统一开发环境就花了两周。后来改用Docker,一天搞定。说白了,Docker就是给你的测试环境拍个快照——谁拉下来都一样。

4.1 为什么选Docker?

嵌入式测试环境有三大痛点:

  • 工具链依赖复杂——GCC版本、交叉编译器、调试器,一个不对就翻车
  • 多项目并行——A项目用ARM GCC 9,B项目用ARM GCC 11,本地装两套容易冲突
  • 新人上手慢——光配环境就能劝退一半实习生

Docker把整个环境打包成一个镜像。你拉下来,跑起来,就能编译、测试、出报告。我在团队里推这个方案后,新人从「配环境三天」变成了「docker run五分钟」。

核心思路:把工具链、测试框架、依赖库全部写进Dockerfile,用docker-compose编排多容器(比如一个跑编译,一个跑测试,一个跑覆盖率)。

4.2 整体架构

先看一张图,理解我们最终要搭成什么样:

基于Docker的嵌入式测试环境架构 主机(Host) Windows / Linux / macOS + Docker Engine Docker Engine 镜像管理 · 容器生命周期 · 网络 · 卷挂载 编译容器 交叉编译器 CMake / Make 目标库文件 测试容器 CUnit / Unity Ceedling / CMock 测试用例执行 报告容器 gcov / lcov 覆盖率解析 HTML报告生成 共享数据卷:源码 / 构建产物 / 测试报告

三个容器通过共享卷交换数据。编译容器产出的二进制,测试容器拿去跑用例,报告容器解析覆盖率。互不干扰,各司其职。

4.3 Dockerfile编写实战

直接上代码。这是我个人习惯的写法——分层构建,把不变的部分放前面,变的部分放后面,充分利用Docker缓存。

# 基础镜像:Ubuntu 20.04 LTS
FROM ubuntu:20.04 AS base

# 避免交互式安装卡住
ENV DEBIAN_FRONTEND=noninteractive

# 安装基础工具
RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    git \
    wget \
    python3 \
    python3-pip \
    && rm -rf /var/lib/apt/lists/*

# ---------- 工具链层 ----------
FROM base AS toolchain

# 安装ARM交叉编译器(以ARM Cortex-M为例)
RUN apt-get update && apt-get install -y \
    gcc-arm-none-eabi \
    gdb-multiarch \
    && rm -rf /var/lib/apt/lists/*

# 验证工具链
RUN arm-none-eabi-gcc --version

# ---------- 测试框架层 ----------
FROM toolchain AS test-framework

# 安装CUnit
RUN apt-get update && apt-get install -y libcunit1-dev && rm -rf /var/lib/apt/lists/*

# 安装Ceedling(Ruby依赖)
RUN apt-get update && apt-get install -y ruby-full && rm -rf /var/lib/apt/lists/*
RUN gem install ceedling

# 安装lcov
RUN apt-get update && apt-get install -y lcov && rm -rf /var/lib/apt/lists/*

# ---------- 工作目录 ----------
WORKDIR /workspace
COPY . .

# 默认命令
CMD ["/bin/bash"]

小技巧:用多阶段构建(FROM ... AS ...)可以减小最终镜像体积。我有个项目从1.2GB压到了380MB。

4.4 docker-compose编排

单容器不够用?那就上docker-compose。下面这个配置,我一直在用:

version: '3.8'

services:
  builder:
    build:
      context: .
      target: toolchain
    volumes:
      - ./src:/workspace/src
      - ./build:/workspace/build
    command: >
      sh -c "cd /workspace/build && cmake .. && make -j$(nproc)"

  tester:
    build:
      context: .
      target: test-framework
    volumes:
      - ./build:/workspace/build
      - ./test:/workspace/test
      - ./reports:/workspace/reports
    depends_on:
      - builder
    command: >
      sh -c "cd /workspace && ctest --output-on-failure"

  reporter:
    build:
      context: .
      target: test-framework
    volumes:
      - ./build:/workspace/build
      - ./reports:/workspace/reports
    depends_on:
      - tester
    command: >
      sh -c "lcov --capture --directory /workspace/build \
             --output-file /workspace/reports/coverage.info \
             && genhtml /workspace/reports/coverage.info \
             --output-directory /workspace/reports/html"

执行顺序:builder → tester → reporter。一步到位。

4.5 工具链安装与验证

工具链装好了,怎么确认没问题?我一般跑三个检查:

检查项 命令 预期输出
编译器版本 arm-none-eabi-gcc --version arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors) 9.3.1
编译测试 echo 'int main(){return 0;}' | arm-none-eabi-gcc -x c - -o /tmp/test.elf 无报错,生成ELF文件
测试框架 ceedling version Ceedling 0.31.1

注意:我曾经遇到过arm-none-eabi-gcc装好了,但gdb-multiarch版本不匹配,导致调试时断点打不上。建议工具链版本统一从官方源或ARM官网下载,别混用。

4.6 避坑指南

这些年踩过的坑,列几个典型的:

  • 权限问题:容器内生成的文件属主是root,宿主机上删不掉。解决方案:创建容器时指定用户ID,或者用docker run --user。
  • 时区不一致:默认UTC,日志时间对不上。加一行ENV TZ=Asia/Shanghai。
  • apt源慢:换国内镜像。在Dockerfile里加RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list。
  • 镜像体积膨胀:每层RUN都会产生新层。用&&合并命令,最后rm -rf /var/lib/apt/lists/*清理缓存。

一句话总结:Dockerfile写清楚,docker-compose编排好,工具链验证通过。你的嵌入式测试环境就稳了。团队里谁拉下来都能跑,再也不用「我机器上明明可以啊」。