TDD方面的知识漏洞

本文关键字:漏洞 知识 方面 TDD | 更新日期: 2023-09-27 18:20:56

我制作了一个列表(示例测试驱动开发中的所谓"测试列表"),从中选择要实现的测试。

所以我启动了Visual Studio,创建了一个新的解决方案,为单元测试添加了一个项目,然后。。。我需要想出一个类,我将把我从列表中选择的测试的测试方法放在其中。

这就是我陷入困境的地方。我如何知道我需要哪个类,如何命名,以及如何知道它是否正确?这是需要事先考虑的事情吗?

TDD方面的知识漏洞

你读过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)轻松模拟依赖关系。

这是需要事先考虑的事情吗?

事先。这就是它称之为测试驱动开发的原因
在开始实施之前,您必须设计工作流。

一个好主意是从您的域开始考虑这一点,而不是一些模糊的测试。例如,您需要使用foo1foo2的功能来开发Foo

因此,您使用foo1Testfoo2Test创建了一个名为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);
    }

由于TransactionSummarizationSummary类还不存在,现在就创建它们。它们看起来是这样的:

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