TDD方面的知识漏洞
本文关键字:漏洞 知识 方面 TDD | 更新日期: 2023-09-27 18:20:56
我制作了一个列表(示例测试驱动开发中的所谓"测试列表"),从中选择要实现的测试。
所以我启动了Visual Studio,创建了一个新的解决方案,为单元测试添加了一个项目,然后。。。我需要想出一个类,我将把我从列表中选择的测试的测试方法放在其中。
这就是我陷入困境的地方。我如何知道我需要哪个类,如何命名,以及如何知道它是否正确?这是需要事先考虑的事情吗?
你读过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 ex,上面的操作将失败,但我有一个测试,并且我还有一个带有方法的接口。我可以继续遵循这个过程来为我的解决方案建模,直到我准备好开发具体的实现为止。在这个过程之后,我关注的是接口和类型之间的交互,而不是那些类型的实现。一旦我构建了Foo,我只需将上面的内容更改为var foo = new Foo();
,并使所有测试都为绿色。这个过程还意味着我为每个类都有一个接口,这在编写单元测试时是必不可少的,因为我可以使用动态模拟库(如MOQ)轻松模拟依赖关系。
这是需要事先考虑的事情吗?
事先。这就是它称之为测试驱动开发的原因
在开始实施之前,您必须设计工作流。
一个好主意是从您的域开始考虑这一点,而不是一些模糊的测试。例如,您需要使用foo1和foo2的功能来开发Foo。
因此,您使用foo1Test
和foo2Test
创建了一个名为FooTest
的测试类。最初这些测试会失败,你只需要努力让它们通过。
您的系统做什么?你可以从那里开始。
假设您正在编写一个功能,该功能读取包含给定账户交易的文档,并生成借方和贷方的汇总摘要。
让我们创建一个测试:
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
类还不存在,现在就创建它们。它们看起来是这样的:
TransactionSummary.java
public class TransactionSummarization {
public Summary summarizeTransactionsIn(Scanner transactionList) {
return null;
}
}
Summary.java
public class Summary {
public double debits;
public double credits;
}
既然已经处理了所有的编译错误,就可以运行测试了。由于summarizeTransactionsIn
方法的空实现,它将以NullPointerException
失败。从方法返回一个摘要实例,然后它通过。
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#,但我希望这无论如何都会有所帮助。:-)
谢谢!
Brandon