我制作了一个列表(在Test-Driven Development by Example中称为“测试列表”),我将从中选择一个测试来实现。
所以我启动Visual Studio,创建一个新的解决方案,为单元测试添加一个项目,然后......我需要提出一个类,我将把测试方法用于我选择的测试清单。
这是我被卡住的地方。我怎么知道我需要哪个课程,如何命名以及如何知道它是否正确?这是事先需要考虑的事情吗?
答案 0 :(得分:3)
你读过Kent Beck - TDD吗?停止尝试提前完成所有工作。潜入,做某事,让它发挥作用,无论它是什么,然后你会更好地了解它应该是什么,你可以改变它。校长是这样,在考虑如何做之前考虑一下你想做什么。写一个测试,测试做你想做的事情,然后实现一个解决方案。你会在第一次,第二次和第三次出错,但是这个过程会让你更接近实际的解决方案,当你完成时,你应该拥有有价值的测试套件和一组松散的几个类来获得这份工作完成。
编辑注释评论
不,不是随机名称。您需要预先执行一定数量的设计。我经常从提出我认为我的解决方案需要的密钥类型开始。然后我开始一个测试类(Say FooTest),在其中我编写了一个我希望Foo做的测试。我使用编写测试的过程来编写接口。 Resharper非常适合这个,因为我可以引用尚不存在的类型和方法,并让Resharper创建它们:
[TestFixture]
public class FooTest
{
[Test]
public void Bar()
{
var foo = (IFoo)null; //At this point I use Resharper to create IFoo interface
Assert.IsTrue(foo.Bar()); //At this point I use Resharper to create bool IFoo.Bar();
}
}
显然上面的代码会因null ref而失败,但我有一个测试,我有一个方法接口。我可以继续按照这个过程来建模我的解决方案,直到我准备开发一个具体的实现。在此过程之后,我将重点关注类型之间的接口和交互,而不是这些类型的实现。一旦我构建了Foo,我只需将上面的内容更改为var foo = new Foo();
并将所有测试都设置为绿色。这个过程也意味着我有一个每个类的接口,这对于编写单元测试是必不可少的,因为我可以使用动态模拟库轻松地模拟依赖项,如MOQ。
答案 1 :(得分:0)
这是否需要事先考虑?
在此之前。这就是它所谓的测试驱动开发...的原因 您必须先设计工作流程 才能开始实施。
答案 2 :(得分:0)
一个好主意是从你的域而不是一些模糊的测试开始考虑这个问题。例如,您需要使用 foo1 和 foo2 的功能开发 Foo 。
因此,您创建了一个名为FooTest
的测试类,其中包含foo1Test
和foo2Test
。最初这些测试会失败,你只是努力让它们通过。
答案 3 :(得分:0)
你的系统做什么?你可以从那里开始。
假设您正在编写一个功能,该功能可读取包含特定帐户交易的文档,并生成借记和贷记的汇总摘要。
让我们创建一个测试:
public class TransactionSummarizationTest {
@Test
public void summarizesAnEmptyDocument() {
TransactionSummarization summarizer = new TransactionSummarization();
Summary s = summarizer.summarizeTransactionsIn(new Scanner());
assertEquals(0.00, s.debits, 0.0);
assertEquals(0.00, s.credits, 0.0);
}
由于TransactionSummarization
和Summary
类尚不存在,您现在可以创建它们。它们看起来像这样:
TransactionSummarization.java
public class TransactionSummarization {
public Summary summarizeTransactionsIn(Scanner transactionList) {
return null;
}
}
Summary.java
public class Summary {
public double debits;
public double credits;
}
现在您已经处理了所有编译错误,您可以运行测试。由于您NullPointerException
方法的空实现,它将失败summarizeTransactionsIn
。从方法中返回一个摘要实例,然后传递。
public Summary summarizeTransactionsIn(Scanner transactionList) {
return new Summary();
}
再次运行测试并通过。
现在你已经完成了第一次测试,接下来是什么?我想我想要通过一次交易来尝试测试。
@Test
public void summarizesDebit() {
TransactionSummarization summarizer = new TransactionSummarization();
Summary s = summarizer.summarizeTransactionsIn(new Scanner("01/01/12,DB,1.00"));
assertEquals(1.00, s.debits, 0.0);
assertEquals(0.00, s.credits, 0.0);
}
运行测试后,我们应该看到它失败了,因为我们没有累积值,只是返回一个新的Summary
public Summary summarizeTransactionsIn(Scanner transactionList) {
String currentLine = transactionList.nextLine();
txAmount = currentLine.split(",")[2];
double amount = Double.parseDouble(txAmount);
return new Summary(amount);
}
在Summary
中修复编译错误并实现构造函数后,您的测试应该再次传递。接下来的测试是什么?我们可以学到什么?好吧,我很好奇这个借方/贷方的事情,所以我们接下来就这样做。
@Test
public void summarizesCredit() {
TransactionSummarization summarizer = new TransactionSummarization();
Summary s = summarizer.summarizeTransactionsIn(new Scanner("01/01/12,CR,1.00"));
assertEquals(0.00, s.debits, 0.0);
assertEquals(1.00, s.credits, 0.0);
}
运行此测试,我们应该看到它失败,因为借记是1.00,但是积分是0.0。与我们想要的完全相反,但完全可以预料到,因为我们没有以任何方式检查交易类型。我们现在就这样做。
public Summary summarizeTransactionsIn(Scanner transactionList) {
double debits = 0.0;
double credits = 0.0;
String currentLine = transactionList.nextLine();
String[] data = currentLine.split(",");
double amount = Double.parseDouble(data[2]);
if("DB".equals(data[1]))
debits += amount;
if("CR".equals(data[1]))
credits += amount;
return new Summary(debits, credits);
}
现在所有的测试都通过了,我们可以继续进行下一次测试。怎么办?我想如果我们希望这个项目成功,那么只处理文件中的一行对我们没有多大帮助。如何同时处理多个记录?我们来写一个测试吧!
@Test
public void summarizesDebitsAndCredits() {
String transactions = "01/01/12,CR,1.75\\n" +
"01/02/12,DB,3.00\\n" +
"01/02/12,DB,2.50\\n" +
"01/02/12,CR,1.25";
TransactionSummarization summarizer = new TransactionSummarization();
Summary s = summarizer.summarizeTransactionsIn(new Scanner(transactions));
assertEquals(5.50, s.debits, 0.0);
assertEquals(3.00, s.credits, 0.0);
}
现在,运行我们所有的测试,我们发现这个测试以可预测的方式失败。它告诉我们,我们的借记是0.00,并且积分是1.75,因为我们只处理了第一条记录。
现在让我们解决这个问题。一个简单的while
循环,我们应该重新开始营业:
public Summary summarizeTransactionsIn(Scanner transactionList) {
double debits = 0.0;
double credits = 0.0;
while(transactionList.hasLine()) {
String currentLine = transactionList.nextLine();
String[] data = currentLine.split(",");
double amount = Double.parseDouble(data[2]);
if("DB".equals(data[1]))
debits += amount;
if("CR".equals(data[1]))
credits += amount;
}
return new Summary(debits, credits);
}
所有测试都通过了,我会把剩下的部分交给你。需要考虑的一些事情是边缘情况,例如他包含混合大小写的文件,例如“cr”与“CR”,或无效/缺失数据等。
另外,在我输入你所指的C#之后,我意识到了。不幸的是我用Java做了它并且懒得把它转换成C#,但我希望无论如何这都有帮助。 : - )
谢谢!
布兰登