在编写单元测试时如何知道要测试啥? [关闭]

Posted

技术标签:

【中文标题】在编写单元测试时如何知道要测试啥? [关闭]【英文标题】:How do you know what to test when writing unit tests? [closed]在编写单元测试时如何知道要测试什么? [关闭] 【发布时间】:2010-09-08 22:09:55 【问题描述】:

使用 C#,我需要一个名为 User 的类,它具有用户名、密码、活动标志、名字、姓氏、全名等。

应该有验证保存用户的方法。我只是为这些方法编写一个测试吗?由于它们是 .Net 的 getter 和 setter,我什至需要担心测试这些属性吗?

【问题讨论】:

这篇文章将有助于缩小更广泛的问题:earnestengineer.blogspot.com/2018/03/…您可以按照这些指南来集中您的问题 请记住,密码不应以明文形式存储。 【参考方案1】:

在编写单元测试或任何测试时,您可以通过查看所测试内容的边界条件来确定要测试的内容。例如,您有一个名为 is_prime 的函数。幸运的是,它按照其名称的含义告诉您整数对象是否为素数。为此,我假设您正在使用对象。现在,我们需要检查已知范围的素数和非素数对象是否发生了有效结果。这就是你的起点。

基本上,看看函数、方法、程序或脚本应该发生什么,然后看看同样的代码绝对应该发生什么。这是你测试的基础。随着您对代码应该发生的事情的了解越来越多,请准备好修改您的测试。

【讨论】:

【参考方案2】:

另一个规范的答案。这一点,我相信,来自 Ron Jeffries:

只测试你想要工作的代码。

【讨论】:

【参考方案3】:

编写没有价值的代码总是一个坏主意。由于建议的测试不会为您的项目增加任何价值(或非常接近它)。那么你就是在浪费宝贵的时间来编写真正带来价值的代码。

【讨论】:

【参考方案4】:

这让我进入了单元测试,这让我很开心

我们刚刚开始进行单元测试。 很长一段时间以来,我都知道开始这样做会很好,但我不知道如何开始,更重要的是要测试什么。

然后我们不得不在我们的会计程序中重写一段重要的代码。 这部分非常复杂,因为它涉及许多不同的场景。 我所说的部分是一种支付已输入会计系统的销售和/或采购发票的方法。

我只是不知道如何开始编写代码,因为有很多不同的付款方式。 一张发票可能是 100 美元,但客户只转了 99 美元。 也许您已经向客户发送了销售发票,但您也从该客户那里购买了产品。 所以你以 300 美元的价格卖掉了他,但你以 100 美元的价格买下了他。您可以期望您的客户向您支付 200 美元来结清余额。 如果你卖了 500 美元,但客户只付给你 250 美元怎么办?

因此,我有一个非常复杂的问题需要解决,其中一种方案可以完美运行,但在另一种类型的发票/付款组合上会出错。

这就是单元测试的用武之地。

我开始(在测试代码中)编写一种方法来创建发票列表,包括销售和采购。 然后我编写了第二种方法来创建实际付款。 通常,用户会通过用户界面输入该信息。

然后我创建了第一个 TestMethod,测试了一个非常简单的单张发票付款,没有任何付款折扣。 当银行付款被保存到数据库时,系统中的所有操作都会发生。 如您所见,我创建了一张发票,创建了一笔付款(银行交易)并将交易保存到磁盘。 在我的断言中,我在银行交易和链接的发票中输入了正确的数字。 我在交易后检查付款次数、付款金额、折扣金额和发票余额。

测试运行后,我会去数据库并仔细检查我所期望的是否存在。

写完测试后,我开始编写支付方式(BankHeader 类的一部分)。 在编码中,我只关心代码以使第一次测试通过。我还没有考虑其他更复杂的场景。

我运行了第一个测试,修复了一个小错误,直到我的测试通过。

然后我开始编写第二个测试,这次是使用付款折扣。 写完测试后,我修改了付款方式以支持折扣。

在使用付款折扣测试正确性的同时,我还测试了简单付款。 当然,这两个测试都应该通过。

然后我逐步解决了更复杂的场景。

1) 想一个新的场景

2) 为该场景编写测试

3) 运行该单一测试以查看它是否会通过

4) 如果没有,我会调试和修改代码,直到它通过。

5) 在修改代码时,我继续运行所有测试

这就是我设法创建非常复杂的付款方式的方法。 如果没有单元测试,我不知道如何开始编码,这个问题似乎很严重。 通过测试,我可以从一个简单的方法开始,然后逐步扩展它,确保更简单的场景仍然有效。

我确信使用单元测试为我节省了几天(或几周)的编码时间,并且或多或少地保证了我的方法的正确性。

如果我以后想到一个新场景,我可以将它添加到测试中,看看它是否有效。 如果不是,我可以修改代码,但仍然可以确保其他方案仍然正常工作。 这将在维护和错误修复阶段节省大量时间。

是的,如果用户做了您没有想到或阻止他做的事情,即使经过测试的代码仍然可能存在错误

以下只是我为测试付款方式而创建的一些测试。

public class TestPayments

    InvoiceDiaryHeader invoiceHeader = null;
    InvoiceDiaryDetail invoiceDetail = null;
    BankCashDiaryHeader bankHeader = null;
    BankCashDiaryDetail bankDetail = null;



    public InvoiceDiaryHeader CreateSales(string amountIncVat, bool sales, int invoiceNumber, string date)
    
        ......
        ......
    

    public BankCashDiaryHeader CreateMultiplePayments(IList<InvoiceDiaryHeader> invoices, int headerNumber, decimal amount, decimal discount)
    
       ......
       ......
       ......
    


    [TestMethod]
    public void TestSingleSalesPaymentNoDiscount()
    
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("119", true, 1, "01-09-2008"));
        bankHeader = CreateMultiplePayments(list, 1, 119.00M, 0);
        bankHeader.Save();

        Assert.AreEqual(1, bankHeader.BankCashDetails.Count);
        Assert.AreEqual(1, bankHeader.BankCashDetails[0].Payments.Count);
        Assert.AreEqual(119M, bankHeader.BankCashDetails[0].Payments[0].PaymentAmount);
        Assert.AreEqual(0M, bankHeader.BankCashDetails[0].Payments[0].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[0].InvoiceHeader.Balance);
    

    [TestMethod]
    public void TestSingleSalesPaymentDiscount()
    
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("119", true, 2, "01-09-2008"));
        bankHeader = CreateMultiplePayments(list, 2, 118.00M, 1M);
        bankHeader.Save();

        Assert.AreEqual(1, bankHeader.BankCashDetails.Count);
        Assert.AreEqual(1, bankHeader.BankCashDetails[0].Payments.Count);
        Assert.AreEqual(118M, bankHeader.BankCashDetails[0].Payments[0].PaymentAmount);
        Assert.AreEqual(1M, bankHeader.BankCashDetails[0].Payments[0].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[0].InvoiceHeader.Balance);
    

    [TestMethod]
    [ExpectedException(typeof(ApplicationException))]
    public void TestDuplicateInvoiceNumber()
    
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("100", true, 2, "01-09-2008"));
        list.Add(CreateSales("200", true, 2, "01-09-2008"));

        bankHeader = CreateMultiplePayments(list, 3, 300, 0);
        bankHeader.Save();
        Assert.Fail("expected an ApplicationException");
    

    [TestMethod]
    public void TestMultipleSalesPaymentWithPaymentDiscount()
    
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("119", true, 11, "01-09-2008"));
        list.Add(CreateSales("400", true, 12, "02-09-2008"));
        list.Add(CreateSales("600", true, 13, "03-09-2008"));
        list.Add(CreateSales("25,40", true, 14, "04-09-2008"));

        bankHeader = CreateMultiplePayments(list, 5, 1144.00M, 0.40M);
        bankHeader.Save();

        Assert.AreEqual(1, bankHeader.BankCashDetails.Count);
        Assert.AreEqual(4, bankHeader.BankCashDetails[0].Payments.Count);
        Assert.AreEqual(118.60M, bankHeader.BankCashDetails[0].Payments[0].PaymentAmount);
        Assert.AreEqual(400, bankHeader.BankCashDetails[0].Payments[1].PaymentAmount);
        Assert.AreEqual(600, bankHeader.BankCashDetails[0].Payments[2].PaymentAmount);
        Assert.AreEqual(25.40M, bankHeader.BankCashDetails[0].Payments[3].PaymentAmount);

        Assert.AreEqual(0.40M, bankHeader.BankCashDetails[0].Payments[0].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[1].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[2].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[3].PaymentDiscount);

        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[0].InvoiceHeader.Balance);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[1].InvoiceHeader.Balance);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[2].InvoiceHeader.Balance);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[3].InvoiceHeader.Balance);
    

    [TestMethod]
    public void TestSettlement()
    
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("300", true, 43, "01-09-2008")); //Sales
        list.Add(CreateSales("100", false, 6453, "02-09-2008")); //Purchase

        bankHeader = CreateMultiplePayments(list, 22, 200, 0);
        bankHeader.Save();

        Assert.AreEqual(1, bankHeader.BankCashDetails.Count);
        Assert.AreEqual(2, bankHeader.BankCashDetails[0].Payments.Count);
        Assert.AreEqual(300, bankHeader.BankCashDetails[0].Payments[0].PaymentAmount);
        Assert.AreEqual(-100, bankHeader.BankCashDetails[0].Payments[1].PaymentAmount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[0].InvoiceHeader.Balance);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[1].InvoiceHeader.Balance);
    

【讨论】:

在您的单元测试中发现了一个错误!您重复这一行,而不是在其中一个中包含 2:Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[3].InvoiceHeader.Balance); 您说:“测试运行后,我会去数据库并仔细检查我所期望的是否存在。”这是系统组件之间集成测试的一个很好的示例,而不是单个代码的独立单元测试。 您还违反了每次测试多个断言的规则。【参考方案5】:

我见过的最好的经验法则是测试所有你无法一眼看出的东西,肯定会正常工作。再多一点,你就可以测试语言/环境了。

【讨论】:

【参考方案6】:

这个问题似乎是一个问题,即在哪里对哪些方法进行测试而哪些不进行测试。

在创建用于赋值的 setter 和 getter 时,考虑到了一致性和未来的增长,并预见到在未来一段时间内,setter/getter 可能会演变成更复杂的操作。为了一致性和未来的增长,对这些方法进行单元测试是有意义的。

代码可靠性,尤其是在进行更改以添加额外功能时,是主要目标。我不知道有人因为在测试方法中包含 setter/getter 而被解雇,但我确信有人希望他们测试过的方法最后他们知道或可以回忆起简单的 set/get 包装器,但那不是更长的情况。

也许团队的另一位成员扩展了 set/get 方法以包含现在需要测试但没有创建测试的逻辑。但是现在您的代码正在调用这些方法,您不知道它们已更改并需要深入测试,并且您在开发和 QA 中进行的测试不会触发缺陷,但发布第一天的真实业务数据会触发触发它。

现在,当 set/get 变形为包含可能失败但单元测试未涵盖的逻辑时,两位队友现在将就谁丢球并未能进行单元测试展开辩论。如果从第一天就在简单的 set/gets 上实施测试,最初编写 set/gets 的队友将更容易摆脱这种干净。

我的观点是,用单元测试覆盖所有方法(即使是微不足道的方法)的几分钟“浪费”时间可能会节省数日的头痛,以及金钱/业务声誉的损失和某人的工作损失。

当初级团队成员将琐碎方法更改为非琐碎方法并提示他们更新测试时,您确实将琐碎方法与单元测试包装在一起这一事实,现在没有人遇到麻烦,因为缺陷被控制到生产中。

我们的编码方式以及从我们的代码中可以看出的纪律可以帮助他人。

【讨论】:

以上是关于在编写单元测试时如何知道要测试啥? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

单元测试2

读《单元测试之道Java版》

专有ide的单元测试[关闭]

TypeScript 中的单元测试 [关闭]

C中单元测试的自动生成器[关闭]

什么是好的单元测试? [关闭]