我正在用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);
}
有关它的任何更正/想法?感谢
答案 0 :(得分:8)
首先测试API的基本原理。
测试零元素。
测试一个元素:
测试&gt; 1个元素:
每个都至少有一个测试用例。
例如(在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;行push()
- count()
== 2 - &gt;行peek()
- T ==预期(最后推送) - &gt;好的(偷看假设搜索是一个错字)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并不是很有趣,也许是有用的,这些接口是通过几代开发人员的痛苦获得的。你不会感到兴奋不已。只需从您编写的应用程序中获取任何类,并尝试编写测试。重构它们会给你带来很多乐趣。