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

时间:2010-05-23 21:16:09

标签: c# java unit-testing tdd

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

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

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

你会怎样解决这个问题?我从来没有理解这个想法是为Stack类的每个方法测试一些极端情况,还是从类中做一些“用例”开始,比如添加10个元素并删除它们。这个想法是什么?要使使用Stack的代码尽可能接近我在实际代码中使用的代码?或者只是简单地“添加一个元素”单元测试,我测试是否通过添加该元素来改变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);
    }

有关它的任何更正/想法?感谢

5 个答案:

答案 0 :(得分:8)

首先测试API的基本原理。

测试零元素。

  • 测试它是否为空。
  • 数量为零。
  • Pop失败。

测试一个元素:

  • 致电推。
  • 测试它不是空的。
  • 测试计数为1。
  • 测试Pop返回元素。
  • 测试它现在是空的。
  • 测试计数现在为0。

测试&gt; 1个元素:

  • 现在按2,测试次数为2。
  • 弹出2并确保它们以LIFO顺序出现。
  • 检查空虚并计算。

每个都至少有一个测试用例。

例如(在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());
}

答案 1 :(得分:4)

如果您更详细地写出每种方法的要求,那么将为您提供有关所需单元测试的更多提示。然后,您可以编写这些测试代码。如果你有一个自动完成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会抱怨没有“大小”变量。我会离开这个,直到您创建测试用例“新创建的堆栈为空”,然后您可以创建变量,因为它的初始化在该测试中得到验证。

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

答案 2 :(得分:1)

我会这样开始:

  • create() - IsEmpty() == true - &gt;行
  • 2x push() - count() == 2 - &gt;行
  • peek() - T ==预期(最后推送) - &gt;好的(偷看假设搜索是一个错字)
  • 2x pop() - count() == 0&amp;&amp; isEmppty - &gt;行

答案 3 :(得分:1)

理想情况下,测试必须涵盖课程的所有功能。他们应该检查每个操作是否按照合同行事。从理论上讲,我认为合同是&lt; prev state,params&gt;之间的映射。到&lt;新州,返回值&gt;。 因此,在设计测试之前,您应该很好地定义所有操作的合同。

以下是上面的堆栈API的一些示例测试:

1)Push应该将Count()返回的值增加1

2)在空堆栈上弹出应该抛出异常

3)Pop应该将Count()返回的值减少1

4)推送x1,x2,...,xn然后弹出它们必须以相反的顺序返回它们xn,...,x1

5)添加元素,验证isEmpty()== false然后弹出所有并验证isEmpty ()==真

6)Seek()不得更改Count()

返回的值

7)对Seek()的连续调用必须返回相同的值 等...

答案 4 :(得分:1)

如果您阅读过the book有关Kent Beck的测试驱动开发的内容,您可能已经注意到本书中经常出现的一个想法:您应该根据自己缺少的内容编写测试。只要您不需要某些东西,就不要编写测试而不执行它。

虽然您的Stack类的实现符合您的需求,但您无需彻底实现它。在引擎盖下,它甚至可以返回常量或什么也不做。

测试不应该成为您开发的开销,它应该加快您的开发速度,在您不想保留所有内容时为您提供支持。

TDD的主要优点在于它使您可以编写可在少量代码中测试的代码,因为通常您不想编写50行代码来测试方法。您越来越关注类之间的接口和功能的分配,因为再一次,您不想编写50行代码来测试方法。

话虽如此,我可以告诉你,通过对超级接口进行单元测试来学习TDD并不是很有趣,也许是有用的,这些接口是通过几代开发人员的痛苦获得的。你不会感到兴奋不已。只需从您编写的应用程序中获取任何类,并尝试编写测试。重构它们会给你带来很多乐趣。