在编写单元测试时,您如何知道要测试什么?[关闭]


127

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

应该有验证保存用户的方法。我是否只为这些方法编写测试?而且,由于它们是.Net的getter和setter,因此我是否还需要担心测试属性?


这篇文章将有助于缩小范围更广的问题:earnestengineer.blogspot.com/2018/03/…您可以按照以下指南来关注您的问题
Lindsay Morsillo

请记住,密码不应以纯文本形式存储。
安德森先生

Answers:


131

我对此问题也做出了许多很好的回答:“ 开始TDD-挑战?解决方案?建议?

我也可以建议您看一下我的博客文章(部分受我的问题启发),对此我得到了很好的反馈。即:

我不知道从哪里开始?

  • 重新开始。仅在编写新代码时考虑编写测试。这可以是旧代码的重新设计,也可以是全新的功能。
  • 从简单开始。不要逃避,试图让自己绕过一个测试框架以及像TDD这样的风格。Debug.Assert工作正常。使用它作为起点。它不会弄乱您的项目或创建依赖项。
  • 开始积极。您正在尝试改善自己的手艺,对此感到满意。我看到很多开发人员乐于停滞不前,不愿意尝试新事物来改善自己。您在做正确的事,请记住这一点,这将有助于阻止您放弃。
  • 开始准备挑战。开始进行测试非常困难。期待挑战,但要记住–挑战可以克服。

只测试您的期望

刚开始时,我遇到了真正的问题,因为我一直坐在那里,试图找出可能发生的所有可能的问题,然后尝试进行测试并修复。这是头痛的快速方法。测试应该是真实的YAGNI过程。如果您知道有问题,请为此编写测试。否则,请不要打扰。

只测试一件事

每个测试用例只能测试一件事。如果发现自己在测试用例名称中加上了“ and”,则说明您做错了。

我希望这意味着我们可以从“ getters and setters”继续前进:)


2
“如果您知道有问题,请为它编写测试。否则,请不要打扰。” 我不同意这种说法。我的印象是,单元测试应涵盖所有可能的执行路径。
科林·布莱奇

3
尽管有些人可能会提倡这样的事情,但我个人却不这样做。我90%的头痛源于仅仅尝试做“一切”。我说的是测试您预期会发生的事情(因此您知道自己将获得正确的价值),但是请不要尝试全力以赴。亚尼
罗伯·库珀,

4
我也主张“测试您的错误”方法。如果我们都有无限的时间和耐心,我们将测试所有可能的执行路径。但是我们没有,因此您必须花最大的力气。
Schwern

63

测试您的代码,而不是语言。

像这样的单元测试:

Integer i = new Integer(7);
assert (i.instanceOf(integer));

仅在编写编译器且instanceof方法不起作用的可能性非零时才有用。

不要测试可以依靠该语言强制执行的内容。在您的情况下,我将专注于您的authenticate和save方法-并编写测试以确保它们可以正常处理所有这些字段中的空值。


1
关于“不要测试框架”的要点-我刚接触时也遇到了一些问题。+1(ed):)
罗伯·库珀

38

这使我进入了单元测试,这让我感到非常高兴

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

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

我只是不知道如何开始编码,因为有很多不同的付款方式。一张发票可能是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);
    }

1
在单元测试中发现一个错误!您重复此行而不是在其中之一中添加2:Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[3].InvoiceHeader.Balance);
Ryan Peschel

2
您说:“测试运行后,我将转到数据库并仔细检查是否存在我期望的值。” 这是系统组件之间集成测试的一个很好的例子,而不是单个代码的隔离单元测试。
JTech

2
您还违反了每个测试不止一项断言的规则。
史蒂夫

13

如果它们确实微不足道,那么就不要打扰测试。例如,如果它们是这样实现的;

public class User
{
    public string Username { get; set; }
    public string Password { get; set; }
}

另一方面,如果您做的很聪明(例如对getter / setter中的密码进行加密和解密),请对其进行测试。


10

规则是您必须测试编写的每个逻辑。如果您在getter和setter中实现了某些特定功能,则我认为它们值得测试。如果它们仅将值分配给某些私有字段,请不要打扰。


6

这个问题似乎是一个问题,一个人在哪里划定了什么方法被测试,哪些没有被测试。

创建价值分配的设置者和获取者时要考虑到一致性和未来的增长,并且可以预见,在将来的一段时间中,设置者/获取者可能会演变为更复杂的操作。出于一致性和未来发展的考虑,将这些方法的单元测试放在适当的位置也是很有意义的。

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

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

现在,这两个队友将辩论谁丢球并未能进行单元测试,而设置/获取的变形包括可能会失败但未包含在单元测试中的逻辑。如果从第一天开始就在简单的“设置/获取”上实施测试,最初编写“设置/获取”的队友将拥有更轻松的时间。

我的观点是,几分钟的“浪费”时间涵盖了所有带有单元测试的方法,甚至是琐碎的方法,这可能会节省几天的头痛,并减少金钱/业务信誉和某人的工作。

而且,当初级合伙人将琐碎的方法更改为非琐碎的方法并提示他们更新测试时,您可能会用普通的方法将琐碎的方法包装到单元测试中,而现在没有人遇到麻烦,因为包含缺陷从生产开始。

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


4

另一个规范的答案。我相信Ron Jeffries的话:

仅测试您要使用的代码。


3

测试样板代码是浪费时间,但是正如Slavo所说,如果您给吸气剂/装料器增加了副作用,那么您应该编写一个测试来伴随该功能。

如果您正在进行测试驱动的开发,则应首先编写合同(例如,接口),然后编写测试以行使该接口,以记录预期的结果/行为。然后自己编写方法,而不用碰到单元测试中的代码。最后,获取一个代码覆盖率工具,并确保您的测试执行代码中的所有逻辑路径。


3

诸如getter和setter之类的琐碎真正的代码,除了设置私有字段外,没有其他行为,因此测试起来过于刻薄。在3.0中,C#甚至有一些语法糖,编译器负责处理私有字段,因此您无需进行编程。

我通常会编写许多非常简单的测试来验证我期望从类中获得的行为。即使是简单的事情,例如将两个数字相加。我在编写一个简单的测试和编写一些代码行之间进行了很多切换。原因是我可以围绕代码进行更改,而不必担心我破坏了我没有想到的东西。


很高兴您对KISS原则有所了解。我经常有一些测试,这些测试实际上是2-3行代码,真正的小型,简单测试。容易gr目结舌,难以突破:) +1'
Rob Cooper

3

您应该测试所有内容。现在您有吸气剂和吸气剂,但是有一天您可能会对其进行一些更改,例如进行验证或其他操作。您今天编写的测试将在明天使用,以确保一切正常进行。编写测试时,您应该忘记诸如“现在很简单”之类的注意事项。在敏捷或测试驱动的上下文中,您应该在假设将来进行重构的情况下进行测试。另外,您是否尝试过输入非常奇怪的值,例如极长的字符串或其他“不良”内容?好吧,您应该……永远不要假设将来会严重滥用您的代码。

总的来说,我发现编写大量的用户测试是很费力的。另一方面,尽管它始终为您提供有关应用程序工作方式的宝贵见解,并帮助您摒弃简单(错误)的假设(例如:用户名的长度始终小于1000个字符)。


3

对于可能会出现在工具箱或开源类型的项目中的简单模块,您应该尽可能多地进行测试,包括琐碎的getter和setter。您要记住的事情是,在编写特定模块时生成单元测试非常简单直接。添加getter和setter是最少的代码,无需过多考虑即可进行处理。但是,将代码放置在较大的系统中后,这种额外的工作可以保护您免受基础系统中的更改(例如,基类中的类型更改)的影响。进行全面测试是完成回归分析的最佳方法。


2

为您的getter和setter编写单元测试没有什么坏处。现在,他们可能只是在后台进行字段获取/设置,但是将来您可能具有验证逻辑或需要测试的属性间依赖关系。现在,您在考虑它的时候编写它会容易得多,如果有时间的话记得记住对其进行改造。


好吧,如果您的getter / setter需要单元测试,则必须与它们关联一些逻辑,这意味着必须在内部编写逻辑,如果他们没有任何逻辑,则无需编写单元测试。
Pop Catalin's

2
他的观点是,逻辑可以在以后添加。
LegendLength

2

通常,当仅为某些值定义方法时,请测试可接受范围之内和之上的值。换句话说,请确保您的方法执行了应做的事情,但仅此而已。这很重要,因为当您要失败时,您想尽早失败。

在继承层次结构中,请确保测试LSP符合性。

对我来说,测试默认的getter和setter方法似乎并不是很有用,除非您打算稍后进行一些验证。


2

如果您认为它可能会损坏,请为此编写一个测试。我通常不测试setter / getter,但是可以说您为User.Name做一个名字和姓氏的连接,我会编写一个测试,以便如果有人更改姓氏和名字的顺序,至少他会知道他改变了一些经过测试的东西。


2

规范的答案是“测试任何可能破坏的东西”。如果您确定属性不会损坏,请不要对其进行测试。

一旦发现某个东西坏了(您发现一个错误),显然这意味着您需要对其进行测试。编写测试以重现该错误,观察它是否失败,然后修复该错误,然后观察测试通过。


1

我了解敏捷开发环境中的单元测试,迈克,是的,您需要测试吸气剂和吸气剂(假设它们是公开可见的)。单元测试的整个概念是将软件单元(在这种情况下为类)作为黑盒进行测试。由于getter和setter在外部可见,因此您需要对它们进行身份验证和保存测试。


1

如果Authenticate和Save方法使用这些属性,则您的测试将间接涉及这些属性。只要这些属性仅提供对数据的访问,那么就不需要显式测试(除非您打算100%覆盖)。


1

我会测试您的吸气剂和吸气剂。根据谁编写代码,有些人会更改getter / setter方法的含义。我已经将变量初始化和其他验证视为getter方法的一部分。为了测试这种事情,您需要对代码进行明确的单元测试。


1

我个人将“测试任何可能破坏的东西”,简单的吸气剂(甚至更好的自动属性)也不会破坏。我从来没有简单的return语句失败过,因此从来没有对其进行过测试。如果获取器中包含计算或其他某种形式的语句,我肯定会为它们添加测试。

我个人使用Moq作为模拟对象框架,然后验证我的对象以应有的方式调用周围的对象。


1

您必须使用UT覆盖该类的每个方法的执行,并检查方法的返回值。这包括getter和setter,尤其是在member(properties)是复杂的类的情况下,这需要在初始化期间分配大量内存。例如,使用一些非常大的字符串(或者带有希腊符号的东西)调用设置器,并检查结果是否正确(不被截断,编码是否正确等)。

如果简单整数也适用-如果您传递长整数而不是整数怎么办?这就是您为UT编写原因的原因:


1

我不会测试属性的实际设置。我将更关注消费者如何填充这些属性,以及它们如何填充它们。在进行任何测试时,您都必须权衡测试时间/成本所带来的风险。


1

您应该尽可能使用单元测试来测试“每个不重要的代码块”。

如果您的属性是微不足道的,并且不太可能有人在其中引入错误,那么应该不对它们进行单元测试是安全的。

您的Authenticate()和Save()方法看起来很适合进行测试。


1

理想情况下,您在编写课程时就应该完成单元测试。使用“测试驱动开发”时,这就是您要做的事情。在实现每个功能点时添加测试,并确保也用测试覆盖边缘情况。

之后编写测试要痛苦得多,但可行。

这是我在您的职位上要做的事情:

  1. 编写一组基本测试来测试核心功能。
  2. 获取NCover并在测试中运行它。此时,您的测试覆盖率可能约为50%。
  3. 继续添加覆盖边缘情况的测试,直到覆盖率达到80%-90%

这应该为您提供了一套不错的单元测试工作集,可以很好地缓冲回归。

这种方法的唯一问题是,必须以这种方式将代码设计为可测试的。如果您在早期就犯了任何耦合错误,那么您将很难轻易获得高覆盖率。

这就是为什么在编写代码之前编写测试非常重要的原因。它迫使您编写松散耦合的代码。


1

不要测试明显起作用的代码(样板代码)。因此,如果您的设置方法和获取方法只是“ propertyvalue = value”和“ return propertyvalue”,则对其进行测试是没有意义的。


1

甚至get / set也会产生奇怪的结果,具体取决于它们的实现方式,因此应将它们视为方法。

每个测试都需要为这些属性指定参数集,同时定义可接受和不可接受的属性,以确保调用以预期的方式返回/失败。

您还需要了解安全陷阱,例如SQL注入示例,并进行测试。

因此,是的,您确实需要担心测试属性。


1

我相信测试吸气剂和设置器只是一个简单的操作,这是愚蠢的。我个人不编写复杂的单元测试来涵盖任何使用模式。我尝试编写足够的测试以确保我已经处理了正常的执行行为以及我能想到的尽可能多的错误情况。我将编写更多的单元测试作为对错误报告的回应。我使用单元测试来确保代码满足要求,并使将来的修改更加容易。当我知道如果我破坏某项测试会失败时,我会更愿意更改代码。


1

我会为您正在为其编写代码的任何东西编写测试,该测试可以在GUI界面之外进行测试。

通常,我编写的任何具有任何业务逻辑的逻辑都放置在另一层或业务逻辑层中。

然后为容易做的事情编写测试。

首先,为“业务逻辑层”中的每个公共方法编写一个单元测试。

如果我有这样的课程:

   public class AccountService
    {
        public void DebitAccount(int accountNumber, double amount)
        {

        }

        public void CreditAccount(int accountNumber, double amount)
        {

        }

        public void CloseAccount(int accountNumber)
        {

        }
    }

在编写任何知道要执行这些操作的代码之前,我要做的第一件事就是开始编写单元测试。

   [TestFixture]
    public class AccountServiceTests
    {
        [Test]
        public void DebitAccountTest()
        {

        }

        [Test]
        public void CreditAccountTest()
        {

        }

        [Test]
        public void CloseAccountTest()
        {

        }
    }

编写测试以验证编写的用于执行某些操作的代码。如果您遍历一组事物并对其进行更改,请编写一个执行相同任务的测试,并声明实际发生的事情。

您还可以采用许多其他方法,例如行为驱动开发(BDD),它涉及更多的内容,而不是从单元测试技能入手的好地方。

因此,故事的寓意是,测试任何可能引起您担心的事情,保持单元测试测试较小的特定事物,很多测试都是好的。

将您的业务逻辑放在用户界面层的外面,这样您就可以轻松地为它们编写测试,这会很不错。

我建议将TestDriven.NetReSharper都轻松集成到Visual Studio中。


1

我建议为Authenticate和Save方法编写多个测试。除了成功案例(提供所有参数,所有内容均拼写正确等)之外,最好对各种失败案例进行测试(参数不正确或丢失,适用时不可用的数据库连接等)。我建议使用NUnit在C#中进行实用单元测试作为参考进行。

就像其他人所说的那样,除非对getter和setter有条件逻辑,否则对getter和setter的单元测试是过大的。


1

尽管可以正确猜测代码需要在哪里进行测试,但我通常认为您需要指标来支持这一猜测。我认为单元测试与代码覆盖率指标紧密相关。

具有大量测试的代码,但覆盖率较小,尚未经过良好的测试。也就是说,覆盖率100%但未测试边界和错误情况的代码也不是很好。

您需要在高覆盖率(最小90%)和可变输入数据之间取得平衡。

记住要测试“垃圾进入”!

同样,除非单元测试检查失败,否则它不是单元测试。没有断言或标记有已知异常的单元测试将仅测试代码在运行时不会消失!

您需要设计测试,以便它们始终报告失败或意外/不需要的数据!


1

它使我们的代码更好...时期!

我们软件开发人员在进行测试驱动开发时忘记的一件事是我们行动的目的。如果已经在生产代码就绪后编写了单元测试,则测试的值将下降(但不会完全丢失)。

本着真正的单元测试精神,这些测试并不是主要用来“测试”我们的更多代码;或获得90%-100%更好的代码覆盖率。这些都是首先编写测试的附带好处。最大的收获是,由于TDD的自然过程,我们的生产代码结尾可以更好地编写。

为了更好地传达这种想法,以下内容可能有助于阅读:

有缺陷的单元测试理论
有目的的软件开发

如果我们认为编写更多的单元测试的行为可以帮助我们获得更高质量的产品,那么我们可能正在遭受“测试驱动开发” 的货物崇拜


我不同意这样的说法,即在生产代码已经就位之后,单元测试就没有价值。这样的断言没有考虑到它们在复制生产中发现的错误条件时的效用,也没有考虑到对从先前开发人员或团队继承的代码的理解。
Scott Lawrence

我可能碰错了。我并不是说在生产代码到位后,单元测试就没有价值。但是,它们的价值下降了。单元测试的最大好处来自于我们让它们推动生产发展时所固有的魔力。
Scott Saad
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.