1. shell 功能测试

1.1 实验目的

利用 C++ 单元测试框架 googletest 的特性,对 Minishell 项目进行系统性的黑盒测试。

1.2 Minishell 项目介绍

Minishell 是一个简化版的 Unix Shell 实现,支持以下功能:

基础命令执行

  • 可以执行绝对路径、相对路径或通过环境变量 PATH 查找的可执行文件(如 /bin/lsls
  • 支持命令参数和选项
  • 支持单引号 ' 和双引号 " 的字符串解析(不支持多行命令)

命令分隔与重定向

  • 使用 ; 分隔多个命令
  • 支持输出重定向 > 和追加重定向 >>
  • 支持输入重定向 <
  • 支持管道 |

环境变量与特殊变量

  • 支持环境变量如 $HOME$PATH
  • 支持特殊变量 $?(上一条命令的返回值)

信号处理

  • Ctrl-C 中断当前程序
  • Ctrl-\ 退出当前程序
  • Ctrl-D 发送 EOF

内置命令(Built-in)

  • echo:输出文本,支持 -n 选项
  • pwd:显示当前工作目录
  • cd:切换目录
  • env:显示环境变量
  • export:设置环境变量
  • unset:删除环境变量
  • exit:退出 shell

1.3 环境配置

本实验需要在 Linux 或 macOS 环境下进行(Windows 用户可使用 WSL)。推荐使用 VSCode 作为代码编辑器,并安装以下插件:

  • C/C++ Extension Pack
  • Python Extension Pack

然后,为了正确编译并使用 gtest 测试 Minishell,你将需要:

  • 支持 C++14 及以上的 C++ 编译器
  • 构建工具 CMake 和 Make
  • Minishell 项目的编译依赖

配置测试环境

对于 Linux 平台,编译器使用 Clang,通过包管理器安装。以 Debian/Ubuntu 为例:

sudo apt install gcc g++ cmake make clang

对于 Mac 平台,我们同样推荐使用包管理器 Homebrew 配置环境。在 Mac 终端内输入

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

以安装 Homebrew。随后安装所需软件:

brew install gcc g++ cmake make clang

为验证构建工具已正确安装,在终端输入

cmake --version
make --version
clang --version

若正确显示版本信息,表示工具已经安装并准备好使用。

编译 Minishell

在项目根目录下进入 source/minishell 目录:

cd source/minishell
make

编译成功后,会在当前目录生成 minishell 可执行文件。你可以手动测试:

./minishell
minishell$ echo "Hello World"
Hello World
minishell$ exit

1.4 GoogleTest 测试框架介绍

在 GoogleTest 中,定义了多种宏来编写测试。针对本实验的 Minishell 测试,我们将重点使用以下功能:

TEST 宏

TEST(TestSuiteName, TestName) {
  ... statements ...
}

在测试套件 TestSuiteName 中定义一个名为 TestName 的测试。

TEST_F 宏(测试夹具)

TEST_F(TestFixtureName, TestName) {
  ... statements ...
}

定义一个使用测试夹具类 TestFixtureName 的测试。测试夹具用于:

  • 在每个测试前进行初始化(如启动 minishell 进程)
  • 在每个测试后进行清理(如关闭进程、删除临时文件)
  • 复用测试代码,减少重复

TEST_P 宏(参数化测试)

TEST_P(TestFixtureName, TestName) {
  ... statements ...
}

定义一个值参数化测试,使用测试夹具类 TestFixtureName。参数化测试的优势:

  • 减少重复代码:相同的测试逻辑可以应用于不同的输入数据
  • 提高测试覆盖率:轻松测试大量输入组合
  • 便于维护:修改测试逻辑时只需改一处

参数化测试特别适合测试 Shell 命令,因为:

  • 同一个命令可能有多种输入组合(参数、选项、边界值)
  • 可以系统性地测试等价类和边界值
  • 便于实施黑盒测试方法

INSTANTIATE_TEST_SUITE_P 宏

INSTANTIATE_TEST_SUITE_P(InstantiationName,
                         TestSuiteName,
                         param_generator)

实例化参数化测试套件,其中 param_generator 可以是:

  • Values(v1, v2, v3, ...): 显式指定参数值
  • ValuesIn(container): 从容器中读取参数
  • Range(begin, end, step): 生成范围内的参数
  • Combine(g1, g2, ...): 组合多个参数生成器(笛卡尔积)

断言宏

GoogleTest 提供了丰富的断言宏用于验证测试结果:

基本断言

  • EXPECT_TRUE(condition): 期望条件为真
  • EXPECT_FALSE(condition): 期望条件为假
  • EXPECT_EQ(val1, val2): 期望两个值相等
  • EXPECT_NE(val1, val2): 期望两个值不相等

EXPECT vs ASSERT

  • EXPECT_*: 失败时记录错误,继续执行后续测试
  • ASSERT_*: 失败时立即终止当前测试用例

更多断言宏可参考 GoogleTest 官方文档

1.5 实验内容

本实验将对 Minishell 的各项功能进行系统性测试,综合运用等价类划分、边界值分析等黑盒测试方法,并使用参数化测试提高测试效率。实验所需文件结构如下:

unittest-cpp/
├── CMakeLists.txt              # CMake 构建配置
├── minishell_test_base.h       # 测试基类(提供辅助函数)
├── minishell_builtin_test.cc   # 内置命令测试(等价类划分)
├── minishell_redirect_test.cc  # 重定向功能测试(组合测试)
└── minishell_pipe_test.cc      # 管道功能测试(决策表测试)

已在 unittest-cpp/minishell_test_base.h 提供了一个测试辅助类来与 Minishell 进程交互,只需修改三个 .cc 文件即可。

1.5.1 内置命令测试

测试目标:在 unittest-cpp/minishell_builtin_test.cc 测试各内置部分命令功能是否正常。

实验要求

  1. 完成所有标记为 TODO 的测试用例
  2. echo 命令至少添加 5 个参数化测试用例,覆盖不同的等价类。输入类型等价类可细分为:
    • 有效等价类:
      • EC1: 普通字符串(无特殊字符)
      • EC2: 包含空格的字符串
      • EC3: 空字符串
      • EC4: 包含环境变量的字符串
      • EC5: 包含特殊字符的字符串(需要引号)
    • 无效等价类:
      • EC6: 未定义的环境变量
  3. cd 命令完成边界值测试,至少包含 5 个测试点

编译运行

cd unittest-cpp
cmake -S . -B build
cmake --build build
cd build && ctest --verbose

1.5.2 重定向功能测试

测试目标unittest-cpp/minishell_redirect_test.cc 测试输入/输出重定向功能(>, >>, <)。

实验要求

  1. 完成所有 TODO 标记的函数
  2. 使用 Combine()Values() 创建至少 6 个组合测试用例

1.5.3 管道功能测试(决策表测试)

测试目标:测试管道 | 功能以及管道与其他功能的组合

决策表分析

管道功能涉及多个条件的组合:

条件C1: 命令1有效C2: 命令2有效C3: 有重定向预期结果
R1YYN成功
R2YYY成功
R3YNN失败
R4NYN失败

实验要求

  1. 完成所有 TODO 标记的测试
  2. 根据决策表实现至少 4 个测试规则

编译运行

cd unittest-cpp
cmake -S . -B build
cmake --build build
cd build && ctest --verbose

Acknowledgement

cclaude42. Minishell: 42 - rewrite a simple shell. https://github.com/cclaude42/minishell