关于c#:编写单元测试时如何知道要测试什么?

关于c#:编写单元测试时如何知道要测试什么?

How do you know what to test when writing unit tests?

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

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


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

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

I Don’t Know Where to Start?

  • Start afresh. Only think about writing tests when you are writing new
    code. This can be re-working of old
    code, or a completely new feature.
  • Start simple. Don’t go running off and trying to get your head round
    a testing framework as well as being
    TDD-esque. Debug.Assert works fine.
    Use it as a starting point. It doesn’t
    mess with your project or create
    dependencies.
  • Start positive. You are trying to improve your craft, feel good about
    it. I have seen plenty of developers
    out there that are happy to stagnate
    and not try new things to better
    themselves. You are doing the right
    thing, remember this and it will help
    stop you from giving up.
  • Start ready for a challenge. It is quite hard to start getting into
    testing. Expect a challenge, but
    remember – challenges can be overcome.

Only Test For What You Expect

I had real problems when I first
started because I was constantly sat
there trying to figure out every
possible problem that could occur and
then trying to test for it and fix.
This is a quick way to a headache.
Testing should be a real YAGNI
process. If you know there is a
problem, then write a test for it.
Otherwise, don’t bother.

Only Test One Thing

Each test case should only ever test
one thing. If you ever find yourself
putting"and" in the test case name,
you’re doing something wrong.

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


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

像这样的单元测试:

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

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

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


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

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

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

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

因此,我有一个非常复杂的问题要解决,有很多可能性,即一种方案可以很好地工作,但在另一种报酬/付款组合上却是错误的。

这是拯救单元测试的地方。

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

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

测试运行后,我将转到数据库并仔细检查是否存在我期望的值。

编写测试后,我开始对付款方式(BankHeader类的一部分)进行编码。
在编码中,我只为编写第一遍测试代码而烦恼。我还没有考虑其他更复杂的方案。

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

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

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

然后,我继续研究更复杂的场景。

1)考虑一个新场景

2)针对该场景编写测试

3)运行单个测试,看是否可以通过

4)如果没有,我将调试并修改代码,直到通过为止。

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

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

我确信使用单元测试可以节省几天(或几周)的编码,或多或少可以保证我的方法的正确性。

如果以后再考虑一种新情况,可以将其添加到测试中以查看其是否正常工作。
如果没有,我可以修改代码,但仍要确保其他情况仍然可以正常工作。
这样可以节省维护和错误修复阶段的时间。

是的,如果用户执行了您未想到的或阻止其执行的操作,即使经过测试的代码仍然可能存在错误

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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
3
4
5
public class User
{
    public string Username { get; set; }
    public string Password { get; set; }
}

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


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


这个问题似乎是一个问题,那就是在哪里划定了哪些方法要测试而哪些没有测试。

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

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

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

现在,这两个队友将辩论谁丢球并未能进行单元测试,而设置/获取变型为包括可能失败但未包含在单元测试中的逻辑。如果从第一天开始就对简单的设置/获取执行测试,那么最初编写该设置/获取的团队成员将拥有更加轻松的时间。

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

初级团队的同伴可能会看到您将普通方法与单元测试包装在一起的事实,当他们将普通方法更改为非普通方法并提示他们更新测试时,现在没有人遇到麻烦,因为包含了缺陷从生产开始。

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


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

Only test the code that you want to work.


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

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


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

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


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


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

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


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


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

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

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


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

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


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


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

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

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


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


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


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


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

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


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

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


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

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

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

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


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


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

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

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

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

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

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


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

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

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

  • 编写一组基本测试来测试核心功能。
  • 获取NCover并在测试中运行它。此时,您的测试覆盖率可能约为50%。
  • 继续添加覆盖边缘情况的测试,直到覆盖率达到80%-90%
  • 这应该为您提供了一个不错的单元测试集,可以很好地缓冲回归。

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

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


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


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

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

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

    记住要测试"垃圾进入"!

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

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


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

    就像其他人所说的那样,对getter和setter进行单元测试是过分的,除非您的getter和setter中有条件逻辑。


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


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

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

    然后,为易于执行的操作编写测试。

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

    如果我有这样的课程:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
       public class AccountService
        {
            public void DebitAccount(int accountNumber, double amount)
            {

            }

            public void CreditAccount(int accountNumber, double amount)
            {

            }

            public void CloseAccount(int accountNumber)
            {

            }
        }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
       [TestFixture]
        public class AccountServiceTests
        {
            [Test]
            public void DebitAccountTest()
            {

            }

            [Test]
            public void CreditAccountTest()
            {

            }

            [Test]
            public void CloseAccountTest()
            {

            }
        }

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

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

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

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

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


    测试一个类应验证:

  • 方法和属性返回期望值
  • 提供无效参数时会抛出适当的异常
  • 当调用给定方法时,类与其他对象之间的交互按预期发生
  • 当然,如果getter和setter没有特殊的逻辑,则Authenticate和Save方法的测试应涵盖它们,否则应编写显式测试


    我第二次测试任何可能破坏并且不会编写愚蠢的测试的东西。但是最重??要的原则是测试发现的任何损坏的东西:如果某种方法的行为异常,请编写测试以概述导致失败的数据集,然后更正错误并观察绿色。还测试"边界"数据值(null,0,MAX_INT,空列表等)。


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

    基本上,看一下函数,方法,程序或脚本应该发生什么,然后再看同一代码绝对不应该发生什么。这就是测试的基础。当您对代码将要发生的事情有了更多的了解时,只要准备好修改测试即可。


    编写没有价值的代码总是一个坏主意。由于建议的测试不会给您的项目带来任何价值(或非常接近)。这样一来,您就浪费了宝贵的时间,可以花时间编写实际上可以带来价值的代码。


    我见过的最好的经验法则是测试一下您一眼就无法说出的所有内容,以确保它们可以正常工作。除此之外,您还可以测试语言/环境。


    我不能具体说C#,但是当我编写单元测试时,我会测试每个输入,甚至是用户不进行的输入,这样我就知道如何避免自己的错误。


    推荐阅读