1. 概述
本教程将带你使用测试驱动开发(TDD)流程,一步步实现一个自定义的List
。
⚠️ 这不是TDD入门教程,我们假设你已经了解TDD的基本概念,并希望提升相关实践能力。
简单说,TDD是一种设计工具,能帮助我们通过测试驱动实现。
提前说明:我们重点展示TDD实践而非高效实现,所以性能优化不是本文目标。
2. 准备工作
先定义类的基本骨架:
public class CustomList<E> implements List<E> {
private Object[] internal = {};
// 空实现的方法
}
CustomList
实现了List
接口,因此必须包含该接口声明的所有方法。为通过编译,我们先提供空方法体:有返回值的方法可返回任意默认值(如Object
返回null
,boolean
返回false
)。
为简洁起见,我们省略了可选方法和部分不常用的必选方法。
3. TDD循环流程
使用TDD开发意味着:
- 先写测试 → 用测试定义需求
- 再实现功能 → 让测试通过(代码优雅性暂不考虑)
- 最后重构 → 提升代码可读性和可维护性(保持测试通过)
我们将通过几个List
接口方法演示这个循环,从最简单的开始。
4. isEmpty
方法
isEmpty
可能是List
接口最简单的方法。以下是初始实现:
@Override
public boolean isEmpty() {
return false;
}
这个初始实现足够编译,后续测试会"逼"我们改进它。
4.1 第一个循环
写测试验证空列表应返回true
:
@Test
public void givenEmptyList_whenIsEmpty_thenTrueIsReturned() {
List<Object> list = new CustomList<>();
assertTrue(list.isEmpty());
}
测试失败(方法总是返回false
)。简单粗暴地修复:
@Override
public boolean isEmpty() {
return true;
}
4.2 第二个循环
添加测试验证非空列表应返回false
:
@Test
public void givenNonEmptyList_whenIsEmpty_thenFalseIsReturned() {
List<Object> list = new CustomList<>();
list.add(null);
assertFalse(list.isEmpty());
}
现在需要实现add
方法:
@Override
public boolean add(E element) {
return false;
}
这个实现无效(未修改内部结构)。改进它:
@Override
public boolean add(E element) {
internal = new Object[] { element };
return false;
}
测试仍然失败(isEmpty
未改进)。继续修复:
@Override
public boolean isEmpty() {
if (internal.length != 0) {
return false;
} else {
return true;
}
}
✅ 非空测试通过。
4.3 重构
当前代码不够优雅,优化一下:
@Override
public boolean isEmpty() {
return internal.length == 0;
}
✅ 测试仍然通过,isEmpty
方法完成。
5. size
方法
初始实现仅用于编译:
@Override
public int size() {
return 0;
}
5.1 第一个循环
利用已有的add
方法测试单元素列表大小:
@Test
public void givenListWithAnElement_whenSize_thenOneIsReturned() {
List<Object> list = new CustomList<>();
list.add(null);
assertEquals(1, list.size());
}
测试失败(返回0)。修复实现:
@Override
public int size() {
if (isEmpty()) {
return 0;
} else {
return internal.length;
}
}
5.2 重构
简化实现:
@Override
public int size() {
return internal.length;
}
✅ 方法实现完成。
6. get
方法
初始实现:
@Override
public E get(int index) {
return null;
}
6.1 第一个循环
测试单元素列表获取:
@Test
public void givenListWithAnElement_whenGet_thenThatElementIsReturned() {
List<Object> list = new CustomList<>();
list.add("baeldung");
Object element = list.get(0);
assertEquals("baeldung", element);
}
简单实现通过测试:
@Override
public E get(int index) {
return (E) internal[0];
}
6.2 改进
通常应先写更多测试再改进,但需要其他方法配合(这些方法尚未成熟)。这里我们打破TDD循环,直接完善实现——其实很简单:
@Override
public E get(int index) {
return (E) internal[index];
}
⚠️ 注意:实际项目中应严格遵循TDD循环。
7. add
方法
当前实现(来自第4节):
@Override
public boolean add(E element) {
internal = new Object[] { element };
return false;
}
7.1 第一个循环
测试返回值:
@Test
public void givenEmptyList_whenElementIsAdded_thenGetReturnsThatElement() {
List<Object> list = new CustomList<>();
boolean succeeded = list.add(null);
assertTrue(succeeded);
}
修改返回值:
@Override
public boolean add(E element) {
internal = new Object[] { element };
return true;
}
✅ 测试通过,但存在明显问题:添加第二个元素会覆盖第一个。
7.2 第二个循环
添加测试验证多元素支持:
@Test
public void givenListWithAnElement_whenAnotherIsAdded_thenGetReturnsBoth() {
List<Object> list = new CustomList<>();
list.add("baeldung");
list.add(".com");
Object element1 = list.get(0);
Object element2 = list.get(1);
assertEquals("baeldung", element1);
assertEquals(".com", element2);
}
测试失败(当前实现无法添加多个元素)。修复实现:
@Override
public boolean add(E element) {
Object[] temp = Arrays.copyOf(internal, internal.length + 1);
temp[internal.length] = element;
internal = temp;
return true;
}
✅ 实现已足够优雅,无需重构。
8. 总结
本文演示了通过TDD流程实现自定义List
的过程。使用TDD可以:
✅ 逐步实现需求
✅ 保持高测试覆盖率
✅ 确保代码可测试性(因为代码是为通过测试而写)
⚠️ 本文实现的类仅作演示,实际项目中请勿直接使用。
完整源码(包括省略的测试和实现方法)见GitHub仓库。