How to TDD a List Implementation in Java – 如何在Java中TDD实现一个列表

最后修改: 2018年 3月 3日

中文/混合/英文(键盘快捷键:t)

 

1. Overview

1.概述

In this tutorial, we’ll walk through a custom List implementation using the Test-Driven Development (TDD) process.

在本教程中,我们将使用测试驱动开发(TDD)流程,完成一个自定义List的实现。

This is not an intro to TDD, so we’re assuming you already have some basic idea of what it means and the sustained interest to get better at it.

这不是对TDD的介绍,所以我们假设你已经对它的含义有了一些基本的了解,并且有持续的兴趣来更好地掌握它。

Simply put, TDD is a design tool, enabling us to drive our implementation with the help of tests.

简单地说,TDD是一种设计工具,使我们能够在测试的帮助下驱动我们的实现

A quick disclaimer – we’re not focusing on creating efficient implementation here – just using it as an excuse to display TDD practices.

一个简短的免责声明–我们在这里并不专注于创造高效的实现–只是把它作为展示TDD实践的一个借口。

2. Getting Started

2.开始

First, let’s define the skeleton for our class:

首先,让我们为我们的类定义一个骨架。

public class CustomList<E> implements List<E> {
    private Object[] internal = {};
    // empty implementation methods
}

The CustomList class implements the List interface, hence it must contain implementations for all the methods declared in that interface.

CustomList类实现了List接口,因此它必须包含该接口中声明的所有方法的实现。

To get started, we can just provide empty bodies for those methods. If a method has a return type, we can return an arbitrary value of that type, such as null for Object or false for boolean.

为了入门,我们可以只为这些方法提供空的主体。如果一个方法有一个返回类型,我们可以返回一个该类型的任意值,例如null用于Objectfalse用于boolean

For the sake of brevity, we’ll omit optional methods, together with some obligatory methods that aren’t often used.

为了简洁起见,我们将省略可选的方法,以及一些不常使用的强制性方法。

3. TDD Cycles

3.TDD循环

Developing our implementation with TDD means that we need to create test cases first, thereby defining requirements for our implementation. Only then we’ll create or fix the implementation code to make those tests pass.

用TDD开发我们的实现意味着我们需要首先创建测试用例,从而为我们的实现定义需求。只有然后我们才会创建或修复实现代码以使这些测试通过。

In a very simplified manner, the three main steps in each cycle are:

以一种非常简化的方式,每个周期的三个主要步骤是。

  1. Writing tests – define requirements in the form of tests
  2. Implementing features – make the tests pass without focusing too much on the elegance of the code
  3. Refactoring – improve the code to make it easier to read and maintain while still passing the tests

We’ll go through these TDD cycles for some methods of the List interface, starting with the simplest ones.

我们将对List接口的一些方法进行这些TDD循环,从最简单的方法开始。

4. The isEmpty Method

4、isEmpty方法

The isEmpty method is probably the most straightforward method defined in the List interface. Here’s our starting implementation:

isEmpty方法可能是List接口中定义的最直接的方法。下面是我们的起始实现。

@Override
public boolean isEmpty() {
    return false;
}

This initial method definition is enough to compile. The body of this method will be “forced” to improve when more and more tests are added.

这个最初的方法定义就足以进行编译了。当越来越多的测试被加入时,这个方法的主体将被 “强制 “改进。

4.1. The First Cycle

4.1.第一个周期

Let’s write the first test case which makes sure that the isEmpty method returns true when the list doesn’t contain any element:

让我们写第一个测试案例,确保当列表不包含任何元素时,isEmpty方法返回true

@Test
public void givenEmptyList_whenIsEmpty_thenTrueIsReturned() {
    List<Object> list = new CustomList<>();

    assertTrue(list.isEmpty());
}

The given test fails since the isEmpty method always returns false. We can make it pass just by flipping the return value:

由于isEmpty方法总是返回false,所以给出的测试失败。我们可以通过翻转返回值来使其通过。

@Override
public boolean isEmpty() {
    return true;
}

4.2. The Second Cycle

4.2.第二个周期

To confirm that the isEmpty method returns false when the list isn’t empty, we need to add at least one element:

为了确认isEmpty方法在列表不为空时返回false,我们需要添加至少一个元素。

@Test
public void givenNonEmptyList_whenIsEmpty_thenFalseIsReturned() {
    List<Object> list = new CustomList<>();
    list.add(null);

    assertFalse(list.isEmpty());
}

An implementation of the add method is now required. Here’s the add method we start with:

现在需要一个add方法的实现。下面是我们开始使用的add方法。

@Override
public boolean add(E element) {
    return false;
}

This method implementation doesn’t work as no changes to the internal data structure of the list are made. Let’s update it to store the added element:

这个方法的实现并不奏效,因为没有对列表的内部数据结构进行改变。让我们来更新它以存储新增的元素。

@Override
public boolean add(E element) {
    internal = new Object[] { element };
    return false;
}

Our test still fails since the isEmpty method hasn’t been enhanced. Let’s do that:

我们的测试仍然失败,因为isEmpty方法还没有被增强。让我们来做这件事。

@Override
public boolean isEmpty() {
    if (internal.length != 0) {
        return false;
    } else {
        return true;
    }
}

The non-empty test passes at this point.

这时,非空测试通过。

4.3. Refactoring

4.3.重构

Both test cases we’ve seen so far pass, but the code of the isEmpty method could be more elegant.

到目前为止,我们看到的两个测试案例都通过了,但isEmpty方法的代码可以更优雅。

Let’s refactor it:

让我们重构它。

@Override
public boolean isEmpty() {
    return internal.length == 0;
}

We can see that tests pass, so the implementation of the isEmpty method is complete now.

我们可以看到测试通过了,所以现在isEmpty方法的实现已经完成。

5. The size Method

5.大小方法

This is our starting implementation of the size method enabling the CustomList class to compile:

这是我们对size方法的起始实现,使CustomList类得以编译。

@Override
public int size() {
    return 0;
}

5.1. The First Cycle

5.1.第一个周期

Using the existing add method, we can create the first test for the size method, verifying that the size of a list with a single element is 1:

使用现有的 add 方法,我们可以为 size 方法创建第一个测试,验证一个有单个元素的 list 的大小是 1

@Test
public void givenListWithAnElement_whenSize_thenOneIsReturned() {
    List<Object> list = new CustomList<>();
    list.add(null);

    assertEquals(1, list.size());
}

The test fails as the size method is returning 0. Let’s make it pass with a new implementation:

由于size方法返回0,测试失败。让我们用一个新的实现使它通过。

@Override
public int size() {
    if (isEmpty()) {
        return 0;
    } else {
        return internal.length;
    }
}

5.2. Refactoring

5.2.重构

We can refactor the size method to make it more elegant:

我们可以重构size方法,使其更加优雅。

@Override
public int size() {
    return internal.length;
}

The implementation of this method is now complete.

这一方法的实施现在已经完成。

6. The get Method

6、get方法

Here’s the starting implementation of get:

这里是get的起始实现。

@Override
public E get(int index) {
    return null;
}

6.1. The First Cycle

6.1.第一个周期

Let’s take a look at the first test for this method, which verifies the value of the single element in the list:

让我们看一下这个方法的第一个测试,它验证了列表中单个元素的值。

@Test
public void givenListWithAnElement_whenGet_thenThatElementIsReturned() {
    List<Object> list = new CustomList<>();
    list.add("baeldung");
    Object element = list.get(0);

    assertEquals("baeldung", element);
}

The test will pass with this implementation of the get method:

用这个实现的get方法,测试将通过。

@Override
public E get(int index) {
    return (E) internal[0];
}

6.2. Improvement

6.2.改进

Usually, we’d add more tests before making additional improvements to the get method. Those tests would need other methods of the List interface to implement proper assertions.

通常,在对get方法进行额外改进之前,我们会添加更多的测试。这些测试需要List接口的其他方法来实现适当的断言。

However, these other methods aren’t mature enough, yet, so we break the TDD cycle and create a complete implementation of the get method, which is, in fact, not very hard.

然而,这些其他的方法还不够成熟,所以我们打破TDD循环,创建一个完整的get方法的实现,事实上,这并不难。

It’s easy to imagine that get must extract an element from the internal array at the specified location using the index parameter:

很容易想象,get必须使用index参数在指定位置从internal数组中提取一个元素。

@Override
public E get(int index) {
    return (E) internal[index];
}

7. The add Method

7.添加方法

This is the add method we created in section 4:

这就是我们在第4节创建的add方法。

@Override
public boolean add(E element) {
    internal = new Object[] { element };
    return false;
}

7.1. The First Cycle

7.1.第一个周期

The following is a simple test that verifies the return value of add:

下面是一个简单的测试,验证了add的返回值。

@Test
public void givenEmptyList_whenElementIsAdded_thenGetReturnsThatElement() {
    List<Object> list = new CustomList<>();
    boolean succeeded = list.add(null);

    assertTrue(succeeded);
}

We must modify the add method to return true for the test to pass:

我们必须修改add方法,使其返回true,这样测试才能通过。

@Override
public boolean add(E element) {
    internal = new Object[] { element };
    return true;
}

Although the test passes, the add method doesn’t cover all cases yet. If we add a second element to the list, the existing element will be lost.

虽然测试通过了,但add方法还不能涵盖所有情况。如果我们向列表添加第二个元素,现有的元素就会丢失。

7.2. The Second Cycle

7.2.第二个周期

Here’s another test adding the requirement that the list can contain more than one element:

下面是另一个测试,增加了列表可以包含一个以上元素的要求。

@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);
}

The test will fail since the add method in its current form doesn’t allow more than one element to be added.

测试将失败,因为目前形式的add方法不允许添加一个以上的元素。

Let’s change the implementation code:

让我们改变一下执行代码。

@Override
public boolean add(E element) {
    Object[] temp = Arrays.copyOf(internal, internal.length + 1);
    temp[internal.length] = element;
    internal = temp;
    return true;
}

The implementation is elegant enough, hence we don’t need to refactor it.

这个实现足够优雅,因此我们不需要重构它。

8. Conclusion

8.结论

This tutorial went through a test-driven development process to create part of a custom List implementation. Using TDD, we can implement requirements step by step, while keeping the test coverage at a very high level. Also, the implementation is guaranteed to be testable, since it was created to make the tests pass.

本教程通过测试驱动的开发过程来创建一个自定义List的部分实现。使用TDD,我们可以一步一步地实现需求,同时将测试覆盖率保持在很高的水平。而且,该实现可以保证是可测试的,因为它的创建是为了使测试通过。

Note that the custom class created in this article is just used for demonstration purposes and should not be adopted in a real-world project.

请注意,本文中创建的自定义类只是用于演示,不应该在现实世界的项目中采用。

The complete source code for this tutorial, including the test and implementation methods left out for the sake of brevity, can be found over on GitHub.

本教程的完整源代码,包括为简洁起见而省略的测试和实现方法,可以在GitHub上找到over