使用测试驱动开发实现堆栈

2022-09-03 14:54:07

我正在完成TDD的第一步。问题是(可能每个人都从TDD开始),当我开始在我的项目中工作时,我永远不知道该做什么样的单元测试。

让我们假设我想使用以下方法编写一个Stack类(我选择它,因为它是一个简单的例子):

Stack<T>
 - Push(element : T)
 - Pop() : T
 - Peek() : T
 - Count : int
 - IsEmpty : boolean

你会如何理解这一点?我一直不明白这个想法是要为Stack类的每个方法测试一些角落案例,还是从对类做一些“用例”开始,比如添加10个元素并删除它们。这是什么概念?要使使用堆栈的代码尽可能接近我将在实际代码中使用的代码?或者只是进行简单的“添加一个元素”单元测试,其中我测试是否通过添加该元素来更改IsEmpty和Count?

我应该如何开始呢?

编辑

这是我的粗略测试的实现:

    [TestMethod]
    public void PushTests() {
        StackZ<string> stackz = new StackZ<string>();

        for (int i = 0; i < 5; ++i) {
            int oldSize = stackz.Size;
            stackz.Push(i.ToString());
            int newSize = stackz.Size;
            Assert.AreEqual(oldSize + 1, newSize);
            Assert.IsFalse(stackz.IsEmpty);
        }
    }

    [TestMethod, ExpectedException(typeof(InvalidOperationException))]
    public void PeekTestsWhenEmpty() {
        StackZ<double> stackz = new StackZ<double>();
        stackz.Peek();
    }

    [TestMethod]
    public void PeekTestsWhenNotEmpty() {
        StackZ<int> stackz = new StackZ<int>();
        stackz.Push(5);

        int firstPeekValue = stackz.Peek();

        for (int i = 0; i < 5; ++i) {
            Assert.AreEqual(stackz.Peek(), firstPeekValue);
        }
    }

    [TestMethod, ExpectedException(typeof(InvalidOperationException))]
    public void PopTestsWhenEmpty() {
        StackZ<float> stackz = new StackZ<float>();
        stackz.Pop();
    }

    [TestMethod]
    public void PopTestsWhenNotEmpty() {
        StackZ<int> stackz = new StackZ<int>();

        for (int i = 0; i < 5; ++i) {
            stackz.Push(i);
        }

        for (int i = 4; i >= 0; ++i) {
            int oldSize = stackz.Size;
            int popValue = stackz.Pop();
            Assert.AreEqual(popValue, i);
            int newSize = stackz.Size;
            Assert.AreEqual(oldSize, newSize + 1);
        }

        Assert.IsTrue(stackz.IsEmpty);
    }

对此有任何更正/想法吗?谢谢


答案 1

首先测试 API 的基本原则。

在零元素上进行测试。

  • 测试它是否为空。
  • 计数为零。
  • 弹出失败。

对一个元素进行测试:

  • 呼叫推送。
  • 测试它是否不为空。
  • 测试该计数为 1。
  • 测试 Pop 是否返回该元素。
  • 测试它现在是空的。
  • 测试该计数现在为 0。

>1 元素测试:

  • 现在 Push 2 和测试计数是 2。
  • 弹出 2 并确保它们按后进先出顺序排列。
  • 检查空虚度和计数。

其中每个都将至少是一个测试用例。

例如(在Google的c ++单元测试框架中大致概述):

TEST(StackTest, TestEmpty) {
  Stack s;
  EXPECT_TRUE(s.empty());
  s.push(1);
  EXPECT_FALSE(s.empty());
  s.pop();
  EXPECT_TRUE(s.empty());
}

TEST(StackTest, TestCount) {
  Stack s;
  EXPECT_EQ(0, s.count());
  s.push(1);
  EXPECT_EQ(1, s.count());
  s.push(2);
  EXPECT_EQ(2, s.count());
  s.pop();
  EXPECT_EQ(1, s.count());
  s.pop();
  EXPECT_EQ(0, s.count());
}

TEST(StackTest, TestOneElement) {
  Stack s;
  s.push(1);
  EXPECT_EQ(1, s.pop());
}

TEST(StackTest, TestTwoElementsAreLifo) {
  Stack s;
  s.push(1);
  s.push(2);
  EXPECT_EQ(2, s.pop());
  EXPECT_EQ(1, s.pop());
}

TEST(StackTest, TestEmptyPop) {
  Stack s;
  EXPECT_EQ(NULL, s.pop());
}


TEST(StackTest, TestEmptyOnEmptyPop) {
 Stack s;
  EXPECT_TRUE(s.empty());
  s.pop();
  EXPECT_TRUE(s.empty());
}

TEST(StackTest, TestCountOnEmptyPop) {
  Stack s;
  EXPECT_EQ(0, s.count());
  s.pop();
  EXPECT_EQ(0, s.count());
}

答案 2

如果您更详细地写出每种方法的要求,这将为您提供有关所需单元测试的更多提示。然后,您可以对这些测试进行编码。如果你有一个自动完成的IDE,比如IDEA,那么做TDD很简单,因为它强调了你尚未实现的所有位。

例如,如果要求是“pop()在空堆栈上抛出一个NoSuchElementException”,那么你可以从

@Test(exception=NoSuchElementException.class)
void popOnEmptyStackThrowsException()
{
   Stack s = new Stack();
   s.pop();
}

然后,IDE 将提示您如何处理缺少的 Stack 类。其中一个选项是“创建类”,因此您可以创建该类。然后,它会询问您也选择创建的 pop 方法。现在,您可以实现 pop 方法,放入实现协定所需的内容。即

T pop() {
   if (size==0) throw new NoSuchElementException();
}

您继续以这种方式迭代,直到您为所有 Stack 要求实施了测试。和以前一样,IDE 会抱怨没有“大小”变量。我会一直保留它,直到你创建测试用例“新创建的堆栈是空的”,然后你可以创建变量,因为它的初始化在该测试中得到了验证。

一旦你的方法要求被处理好了,你就可以添加一些更复杂的用例。(理想情况下,这些用例将指定为类级要求。


推荐