CS61B 课程笔记(Lab 03 Unit Testing with JUnit, Debugging)

1. 实验前准备

  • 安装 IntelliJ 插件:确保你安装并更新了 CS61B 插件(版本 1.0.21+)。
  • 骨架代码:将 Lab 2 中的 IntList.java 文件复制到 lab3/IntList 文件夹中。
  • 课程材料:观看测试相关课程,了解单元测试和 JUnit 的基本概念。

2. 单元测试与 JUnit 介绍

单元测试是什么?

  • 单元测试是对软件中最小的功能单元(通常是方法或类)进行测试的过程。
  • 目的是确保代码模块是独立正确的,从而更容易发现错误并进行调试。

为什么使用单元测试?

  • 早期发现错误:在开发过程中尽早测试和发现潜在错误。
  • 提高代码质量:通过测试驱动开发(TDD)编写更模块化、易测试的代码。
  • 减少调试时间:更快定位问题所在,避免在代码集成后发现重大问题。

JUnit 介绍:

  • JUnit 是一个用于 Java 应用程序的开源测试框架,简化了单元测试编写和执行的过程。
  • 常用注解:
    • @Test:标识一个测试方法。
    • @Before@After:分别在每个测试方法执行前后运行。

JUnit 常用方法:

  • assertEquals(expected, actual):断言两个值相等。
  • assertNotEquals(expected, actual):断言两个值不相等。
  • assertNull(value):断言值为 null
  • assertNotNull(value):断言值不为 null

示例测试方法:

1
2
3
4
@Test
public void testAddition() {
assertEquals(4, 2 + 2);
}

3. 在 IntelliJ 中运行 JUnit 测试

  1. 导入项目:确保正确导入 lab3 文件夹,并检查 javalib 库是否配置好。
  2. 运行测试
    • 打开 lab3/Arithmetic/ArithmeticTest.java
    • 点击测试方法旁边的绿色箭头以运行测试。
    • 可以切换到 jh61b 渲染器,查看测试结果的详细输出。
  3. 分析测试结果
    • 测试通过会显示绿色对勾,失败则会显示红色叉号。
    • 测试失败时,IntelliJ 会提供详细的错误信息和失败的行号,便于定位问题。

4. 调试失败的测试

  1. 识别错误
    • 测试失败时,错误日志会显示预期结果和实际结果的差异。
    • 点击失败的测试可以跳转到具体代码行。
  2. 修复 Bug
    • 检查被测方法中的逻辑错误,尤其是与测试用例不匹配的部分。
    • 修复代码后,重新运行测试,确保错误得到解决。

5. 使用 TDD 进行链表反转

5.1 任务描述
  • 目标:编写一个破坏性方法 IntList.reverse(),反转链表中的元素顺序,并修改原链表。
  • 破坏性方法:即该方法会修改输入的链表,而不是返回一个新的链表。
5.2 编写测试用例

在编写 reverse() 方法之前,先为它编写单元测试。这个方法需要处理三种情况:

  1. 正常的链表反转。
  2. 确保方法破坏性地修改了原链表。
  3. 当输入 null 时,方法返回 null

IntListTest.java 中为 reverse() 方法编写如下测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testReverse() {
IntList list = IntList.of(1, 2, 3);
IntList reversedList = IntList.reverse(list);

// 反转后的链表应为 [3, 2, 1]
assertEquals(IntList.of(3, 2, 1), reversedList);

// 确保原链表被修改
assertNotEquals(list, reversedList);

// 处理 null 输入
assertNull(IntList.reverse(null));
}
5.3 编写 Reverse 方法

接下来在 IntList.java 中实现 reverse() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static IntList reverse(IntList list) {
if (list == null) {
return null;
}

IntList prev = null;
IntList current = list;

while (current != null) {
IntList next = current.rest;
current.rest = prev;
prev = current;
current = next;
}

return prev;
}

此方法的工作原理是通过迭代反转链表,将当前节点的 rest 指针指向前一个节点(prev),直到链表的所有节点都被反转。

5.4 运行测试并验证
  • 运行测试,确保所有测试用例都能通过。
  • 如果测试失败,返回调试错误,检查代码实现中的问题。

6. 小结与反思

  • 本次实验通过测试驱动开发(TDD)的方式,练习了如何使用 JUnit 进行单元测试,以及如何通过测试驱动开发实现链表反转功能。
  • 测试驱动开发(TDD)流程
    1. 编写测试。
    2. 运行测试,确保测试失败。
    3. 实现方法,修复错误。
    4. 重新运行测试,确保测试通过。
  • 实验重点在于编写高质量的测试用例和调试测试失败的能力。