129

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

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

4

36 回答 36

133

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

我还可以推荐看看我的博客文章(部分是受我的问题启发的),我对此有一些很好的反馈。即:

我不知道从哪里开始?

  • 重新开始。仅在编写新代码时才考虑编写测试。这可以是旧代码的重新工作,也可以是全新的功能。
  • 从简单开始。不要跑掉并试图让你的头脑围绕一个测试框架以及 TDD 风格。Debug.Assert 工作正常。以它为起点。它不会弄乱您的项目或创建依赖项。
  • 开始积极。你正在努力提高你的手艺,对此感觉良好。我见过很多开发人员乐于停滞不前,而不是尝试新事物来改善自己。你在做正确的事,记住这一点,这将有助于阻止你放弃。
  • 开始准备迎接挑战。开始测试是相当困难的。期待挑战,但请记住——挑战是可以克服的。

只测试你所期望的

当我刚开始时,我遇到了真正的问题,因为我经常坐在那里试图找出可能发生的每一个可能的问题,然后尝试测试并修复它。这是一种快速解决头痛的方法。测试应该是一个真正的 YAGNI 过程。如果你知道有问题,那就为它写一个测试。否则,不要打扰。

只测试一件事

每个测试用例应该只测试一件事。如果您发现自己在测试用例名称中添加了“and”,那么您就做错了。

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

于 2008-09-15T13:18:40.780 回答
63

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

像这样的单元测试:

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

instanceof仅当您正在编写编译器并且您的方法不工作的可能性非零时才有用。

不要测试可以依靠语言来强制执行的东西。在您的情况下,我将专注于您的身份验证和保存方法 - 我会编写测试以确保它们可以优雅地处理任何或所有这些字段中的空值。

于 2008-09-15T13:09:07.760 回答
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);
    }
于 2008-09-16T03:52:41.787 回答
13

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

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

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

于 2008-09-15T13:10:12.360 回答
10

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

于 2008-09-15T13:07:50.790 回答
6

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

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

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

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

这两个队友现在将争论谁丢球并且没有进行单元测试,当集合/得到变形以包含可能失败但未被单元测试覆盖的逻辑时。如果从第一天就在简单的 set/gets 上实施测试,最初编写 set/gets 的队友将更容易摆脱这种干净。

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

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

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

于 2015-03-21T12:55:23.480 回答
4

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

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

于 2008-09-15T19:04:27.953 回答
3

测试样板代码是浪费时间,但正如 Slavo 所说,如果你给你的 getter/setter 添加了副作用,那么你应该编写一个测试来伴随该功能。

如果你在做测试驱动的开发,你应该首先编写契约(例如接口),然后编写测试来执行记录预期结果/行为的接口。然后自己编写您的方法,而不涉及单元测试中的代码。最后,获取一个代码覆盖工具并确保您的测试运行代码中的所有逻辑路径。

于 2008-09-15T13:12:21.590 回答
3

像 getter 和 setter 这样非常琐碎的代码,除了设置私有字段之外没有额外的行为,测试起来有点过头了。在 3.0 C# 中甚至有一些语法糖,编译器负责处理私有字段,因此您不必对其进行编程。

我通常会编写许多非常简单的测试来验证我期望从我的课程中获得的行为。即使它是简单的东西,比如添加两个数字。我在写一个简单的测试和写几行代码之间切换了很多。这样做的原因是我可以改变代码而不用担心我破坏了我没有想到的东西。

于 2008-09-15T13:14:54.190 回答
3

你应该测试一切。现在你有 getter 和 setter,但有一天你可能会稍微改变它们,也许是为了做验证或其他事情。您今天编写的测试将在明天使用,以确保一切正常工作。当您编写测试时,您应该忘记诸如“现在它是微不足道的”之类的考虑。在敏捷或测试驱动的上下文中,您应该假设未来的重构进行测试。另外,您是否尝试过输入非常奇怪的值,例如极长的字符串或其他“坏”内容?那么你应该......永远不要假设你的代码将来会被滥用到多么严重。

一般来说,我发现编写大量的用户测试是一方面,令人筋疲力尽。另一方面,尽管它总是让您了解应用程序应该如何工作,并帮助您摒弃简单(和错误)的假设(例如:用户名的长度始终小于 1000 个字符)。

于 2008-09-15T14:01:08.587 回答
3

对于可能最终出现在工具包或开源类型项目中的简单模块,您应该尽可能多地进行测试,包括琐碎的 getter 和 setter。您要记住的是,在编写特定模块时生成单元测试非常简单直接。添加 getter 和 setter 是最少的代码,可以不加思索地处理。但是,一旦您的代码被放置在一个更大的系统中,这种额外的努力可以保护您免受底层系统的更改,例如基类中的类型更改。测试一切是完成回归的最佳方法。

于 2008-09-15T14:10:00.767 回答
2

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

于 2008-09-15T13:08:10.073 回答
2

通常,当仅为某些值定义方法时,请在可接受范围内测试值。换句话说,确保你的方法做它应该做的,但仅此而已。这很重要,因为当你要失败时,你想早点失败。

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

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

于 2008-09-15T13:14:59.633 回答
2

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

于 2008-09-15T13:49:52.937 回答
2

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

一旦发现有问题(你发现了一个错误),显然这意味着你需要对其进行测试。编写一个测试来重现错误,观察它失败,然后修复错误,然后观察测试通过。

于 2008-09-15T13:51:14.227 回答
1

据我了解敏捷开发环境中的单元测试,Mike,是的,您需要测试 getter 和 setter(假设它们是公开可见的)。单元测试的整个概念是测试软件单元,在这种情况下是一个类,作为一个黑盒。由于 getter 和 setter 是外部可见的,因此您需要与 Authenticate 和 Save 一起测试它们。

于 2008-09-15T13:08:05.330 回答
1

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

于 2008-09-15T13:08:14.283 回答
1

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

于 2008-09-15T13:09:16.170 回答
1

就我个人而言,我会“测试任何可以破坏的东西”并且简单的 getter(甚至更好的自动属性)不会破坏。我从来没有一个简单的返回语句失败,因此从来没有测试过它们。如果 getter 在其中有计算或其他形式的语句,我当然会为它们添加测试。

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

于 2008-09-15T13:09:25.560 回答
1

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

在同样适用的简单整数的情况下 - 如果您传递 long 而不是整数会发生什么?这就是你写 UT 的原因:)

于 2008-09-15T13:10:03.873 回答
1

一个类的测试应该验证:

  1. 方法和属性返回预期值
  2. 提供无效参数时会引发适当的异常
  3. 调用给定方法时,类和其他对象之间的交互按预期发生

当然,如果 getter 和 setter 没有特殊逻辑,那么 Authenticate 和 Save 方法的测试应该涵盖它们,否则应该编写显式测试

于 2008-09-15T13:10:22.970 回答
1

我不会测试属性的实际设置。我会更关心消费者如何填充这些属性,以及它们用什么填充。对于任何测试,您都必须权衡风险与测试的时间/成本。

于 2008-09-15T13:11:04.760 回答
1

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

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

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

于 2008-09-15T13:11:22.687 回答
1

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

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

这是我在你的位置上会做的事情:

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

这应该为您提供一组很好的工作单元测试,它们将作为对回归的良好缓冲。

这种方法的唯一问题是代码必须设计成可以以这种方式进行测试。如果您在早期犯了任何耦合错误,您将无法很容易地获得高覆盖率。

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

于 2008-09-15T13:13:19.827 回答
1

不要测试明显有效的(样板)代码。因此,如果您的 setter 和 getter 只是“propertyvalue = value”和“return propertyvalue”,那么测试它是没有意义的。

于 2008-09-15T13:13:45.643 回答
1

甚至 get / set 也会产生奇怪的后果,这取决于它们是如何实现的,因此它们应该被视为方法。

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

您还需要了解安全问题,例如 SQL 注入,并针对这些问题进行测试。

所以是的,您确实需要担心测试属性。

于 2008-09-15T13:14:07.063 回答
1

我认为当 getter 和 setter 只进行简单操作时测试它们是愚蠢的。就我个人而言,我不会编写复杂的单元测试来涵盖任何使用模式。我尝试编写足够的测试来确保我已经处理了正常的执行行为以及我能想到的尽可能多的错误情况。我将编写更多单元测试作为对错误报告的回应。我使用单元测试来确保代码满足要求并使将来的修改更容易。当我知道如果我破坏某些东西,测试将失败时,我会更愿意更改代码。

于 2008-09-15T13:16:17.277 回答
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 中。

于 2008-09-15T13:23:50.050 回答
1

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

正如其他人所说,getter 和 setter 的单元测试是多余的,除非你的 getter 和 setter 中有条件逻辑。

于 2008-09-15T14:09:04.997 回答
1

虽然可以正确猜测您的代码需要测试的位置,但我通常认为您需要指标来支持这种猜测。在我看来,单元测试与代码覆盖率指标密切相关。

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

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

记得测试“垃圾输入”!

此外,除非检查失败,否则单元测试不是单元测试。没有断言或标记有已知异常的单元测试将简单地测试代码在运行时不会死!

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

于 2008-09-15T14:51:09.747 回答
1

它使我们的代码更好……期间!

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

在单元测试的真正精神中,这些测试主要不是为了“测试”我们更多的代码;或获得 90%-100% 更好的代码覆盖率。这些都是先编写测试的附带好处。最大的回报是,由于 TDD 的自然过程,我们的生产代码最终写得更好。

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

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

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

于 2008-09-15T15:08:31.797 回答
0

我第二次测试任何可能破坏的东西,不要编写愚蠢的测试。但最重要的原则是测试任何你发现有问题的东西:如果某些方法表现得很奇怪,编写一个测试来概述导致它失败的数据集,然后纠正错误并观察栏变绿。还要测试“边界”数据值(null、0、MAX_INT、空列表等)。

于 2008-09-15T15:16:57.767 回答
0

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

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

于 2008-09-15T15:20:28.583 回答
0

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

于 2008-09-15T20:04:54.283 回答
0

我见过的最好的经验法则是测试你无法一眼看出的所有东西,肯定会正常工作。还有更多,您最终会测试语言/环境。

于 2010-04-26T21:44:23.780 回答
-3

我不能专门针对 C# 说话,但是当我编写单元测试时,我会测试每个输入,即使是用户不做的输入,这样我就知道如何防止自己的错误。

于 2008-09-15T13:07:01.667 回答