在编写单元测试时,我们如何知道要测试什么?
使用C#,我需要一个名为User的类,该类具有用户名,密码,活动标志,名字,姓氏,全名等。
应该有验证和保存用户的方法。我是否只为这些方法编写测试?而且,由于它们是.Net的getter和setter,我是否还需要担心测试这些属性?
解决方案
回答
我不能专门讲C语言,但是当我编写单元测试时,我会测试每个输入,即使是用户没有的输入也是如此,这样我就知道如何避免自己的错误。
回答
规则是,我们必须测试编写的每个逻辑。如果我们在getter和setter中实现了某些特定功能,则我认为它们值得测试。如果它们仅将值分配给某些私有字段,请不要打扰。
回答
我了解敏捷开发环境中的单元测试,迈克,是的,我们需要测试吸气剂和吸气剂(假设它们是公开可见的)。单元测试的整个概念是将软件单元(在这种情况下为类)作为黑盒进行测试。由于getter和setter在外部是可见的,因此我们需要对它们进行身份验证和保存测试。
回答
为getter和setter编写单元测试没有什么坏处。目前,他们可能只是在幕后进行字段获取/设置,但是将来我们可能具有验证逻辑或者需要测试的属性间依赖关系。现在,我们在考虑它的时候编写它会更容易,然后如果需要的话记得进行改造。
回答
如果Authenticate和Save方法使用这些属性,则测试将间接涉及这些属性。只要这些属性仅提供对数据的访问,那么就不需要显式测试(除非我们打算100%覆盖)。
回答
测试代码,而不是语言。
像这样的单元测试:
Integer i = new Integer(7); assert (i.instanceOf(integer));
仅在编写编译器并且instanceof
方法不起作用的可能性非零时才有用。
不要测试可以依靠该语言强制执行的内容。在情况下,我将专注于authenticate和save方法,并编写测试以确保它们可以优雅地处理任何或者所有这些字段中的空值。
回答
我会测试吸气剂和吸气剂。取决于谁编写代码,有些人会更改getter / setter方法的含义。我已经将变量初始化和其他验证视为getter方法的一部分。为了测试这种事情,我们需要对这些代码进行显式的单元测试。
回答
我个人将"测试任何可能破坏的东西",简单的吸气剂(甚至更好的自动属性)也不会破坏。我从来没有简单的return语句失败过,因此从来没有对其进行过测试。如果吸气剂内部有计算或者其他形式的语句,我肯定会为它们添加测试。
我个人使用Moq作为模拟对象框架,然后验证我的对象以应有的方式调用周围的对象。
回答
我们必须使用UT覆盖该类的每个方法的执行,并检查方法的返回值。这包括getter和setter,尤其是在member(properties)是复杂的类的情况下,这需要在初始化期间分配大量内存。例如,使用一些非常大的字符串(或者带有希腊符号的字符串)调用设置器,并检查结果是否正确(不被截断,编码良好等)。
如果还应用简单整数,那么如果我们传递的是整型而不是整型,会发生什么呢?这就是我们为UT编写原因的原因:
回答
如果它们确实微不足道,那么就不要打扰测试。例如,如果它们是这样实现的;
public class User { public string Username { get; set; } public string Password { get; set; } }
另一方面,如果我们做的很聪明(例如对getter / setter中的密码进行加密和解密),请对其进行测试。
回答
测试一个类应验证:
- 方法和属性返回期望值
- 提供无效参数时会抛出适当的异常
- 当调用给定方法时,类与其他对象之间的交互按预期发生
当然,如果获取器和设置器没有特殊的逻辑,则Authenticate和Save方法的测试应覆盖它们,否则应编写显式测试
回答
我不会测试属性的实际设置。我将更关注消费者如何填充这些属性,以及它们如何填充它们。在进行任何测试时,我们都必须权衡测试时间/成本所带来的风险。
回答
我们应该尽可能使用单元测试来测试"每个非平凡的代码块"。
如果属性是微不足道的,并且不太可能有人在其中引入错误,那么不要对它们进行单元测试应该是安全的。
Authenticate()和Save()方法看起来很适合进行测试。
回答
测试样板代码是浪费时间,但是正如Slavo所说,如果我们给吸气剂/设置器增加了副作用,那么我们应该编写一个测试来伴随该功能。
如果我们要进行测试驱动的开发,则应首先编写合同(例如,接口),然后编写测试以行使该接口,以记录预期的结果/行为。然后,自己编写方法,而不用碰到单元测试中的代码。最后,抓住一个代码覆盖率工具,并确保测试使用了代码中的所有逻辑路径。
回答
理想情况下,我们在编写课程时就已经完成了单元测试。这就是使用"测试驱动开发"时要做的事情。在实现每个功能点时添加测试,并确保也用测试覆盖边缘情况。
之后编写测试要痛苦得多,但可行。
这是我在职位上要做的事情:
- 编写一组基本测试来测试核心功能。
- 获取NCover并在测试中运行它。此时测试覆盖率可能约为50%。
- 继续添加覆盖边缘情况的测试,直到覆盖率达到80%-90%
这应该为我们提供了一套很好的单元测试工作集,可以很好地缓冲回归。
这种方法的唯一问题是,必须以这种方式将代码设计为可测试的。如果我们在早期就犯了任何耦合错误,那么我们将很难轻易获得高覆盖率。
这就是为什么在编写代码之前编写测试非常重要的原因。它迫使我们编写松散耦合的代码。
回答
不要测试明显起作用的代码(样板代码)。因此,如果设置方法和获取方法只是" propertyvalue = value"和" return propertyvalue",则对其进行测试是没有意义的。
回答
甚至get / set也会产生奇怪的结果,具体取决于它们的实现方式,因此应将它们视为方法。
每个测试都需要为这些属性指定参数集,同时定义可接受和不可接受的属性,以确保调用以预期的方式返回/失败。
我们还需要注意安全陷阱(例如SQL注入示例),并进行测试。
因此,是的,我们确实需要担心测试属性。
回答
诸如getter和setter之类的琐碎真正的代码,除了设置私有字段外,没有其他行为,因此测试起来过于刻板。在3.0中,Ceven有一些语法糖,编译器将照顾私有字段,因此我们无需编程。
我通常会编写许多非常简单的测试,以验证我期望从类中获得的行为。即使是简单的事情,例如将两个数字相加。我在编写一个简单的测试和编写一些代码行之间进行了很多切换。原因是我可以围绕代码进行更改,而不必担心我破坏了我没有想到的东西。
回答
通常,当仅为某些值定义方法时,请测试可接受范围之内和之上的值。换句话说,请确保方法执行了应做的事情,但仅此而已。这很重要,因为当我们要失败时,我们想尽早失败。
在继承层次结构中,请确保测试LSP符合性。
对我来说,测试默认的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"继续前进:)
回答
我将为我们正在编写的任何代码编写测试,该测试可以在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.Net或者ReSharper,因为它们都可以轻松集成到Visual Studio中。
回答
如果我们认为它可能会损坏,请为此编写一个测试。我通常不测试setter / getter,但是可以说我们为User.Name做一个,后者连接了名字和姓氏,我会编写一个测试,以便如果有人更改姓和名的顺序,至少他会他改变了一些经过测试的东西。
回答
规范的答案是"测试任何可能破坏的东西"。如果我们确定属性不会损坏,请不要对其进行测试。
一旦发现某个东西坏了(我们发现了一个错误),显然这意味着我们需要对其进行测试。编写测试以重现该错误,观察它是否失败,然后修复该错误,然后观察测试通过。
回答
我们应该测试所有内容。现在我们有吸气剂和吸气剂,但是有一天我们可能会对其进行一些更改,例如进行验证或者其他操作。我们今天编写的测试将在明天使用,以确保一切正常进行。
编写测试时,我们应该忘记诸如"现在很简单"之类的注意事项。在敏捷或者测试驱动的上下文中,我们应该在假设将来进行重构的情况下进行测试。
另外,我们是否尝试过输入非常奇怪的值(例如,极长的字符串或者其他"不良"内容)?好吧,我们应该……永远不要假设代码将来会被滥用的严重程度。
总的来说,我发现编写大量的用户测试是很费力的。另一方面,尽管它始终为我们提供有关应用程序工作方式的宝贵见解,并摒弃简单(错误的)假设(例如:用户名的长度始终小于1000个字符)。
回答
我建议为Authenticate和Save方法编写多个测试。除了成功案例(提供所有参数,正确拼写所有内容等)之外,还可以对各种失败案例(参数不正确或者缺失,适用的数据库连接不可用等)进行测试。我建议在Cwith NUnit中进行实用单元测试作为参考。
就像其他人所说的那样,除非getter和setter中存在条件逻辑,否则针对getter和setter的单元测试是过大的。
回答
对于可能会出现在工具箱或者开源类型的项目中的简单模块,我们应该尽可能多地进行测试,包括琐碎的getter和setter。我们要记住的事情是,在编写特定模块时生成单元测试非常简单直接。添加getter和setter是最少的代码,无需过多考虑即可进行处理。但是,将代码放置在较大的系统中后,这种额外的工作可以保护我们免受基础系统中的更改(例如,基类中的类型更改)的影响。进行全面测试是使回归完整的最好方法。
回答
尽管可以正确猜测代码需要在哪里进行测试,但我通常认为我们需要指标来支持这一猜测。在我看来,单元测试与代码覆盖率指标紧密相关。
具有大量测试的代码,但覆盖率较小,尚未经过良好的测试。也就是说,覆盖率100%但未测试边界和错误情况的代码也不是很好。
我们希望在高覆盖率(最小90%)和可变输入数据之间取得平衡。
记住要测试"垃圾进入"!
同样,除非单元测试检查失败,否则它不是单元测试。没有断言或者标记有已知异常的单元测试只会测试代码在运行时不会消失!
我们需要设计测试,以便它们始终报告失败或者意外/不需要的数据!
回答
我们软件开发人员在进行测试驱动开发时忘记的一件事是我们行动的目的。如果在生产代码已经就位之后编写单元测试,则测试的值将下降(但不会完全丢失)。
本着真正的单元测试精神,这些测试并不是主要用来"测试"我们的更多代码;或者获得90%-100%更好的代码覆盖率。这些都是首先编写测试的附带好处。最大的收获是,由于TDD的自然过程,我们的生产代码结尾可以更好地编写。
为了更好地传达这一想法,以下内容可能有助于阅读:
有缺陷的单元测试理论
有目的的软件开发
如果我们认为编写更多的单元测试的行为可以帮助我们获得更高质量的产品,那么我们可能正在遭受"测试驱动开发"的"货物崇拜"。
回答
我第二次测试任何可能破坏并且不会编写愚蠢的测试的东西。但是最重要的原则是测试发现的任何损坏的东西:如果某种方法的行为异常,编写一个测试以概述导致失败的数据集,然后更正错误并观察绿色。还测试"边界"数据值(null,0,MAX_INT,空列表等)。
回答
在编写单元测试或者实际上是任何测试时,我们可以通过查看测试对象的边界条件来确定要测试的对象。例如,我们有一个名为is_prime的函数。幸运的是,它执行了其名称所隐含的功能,并告诉我们整数对象是否为质数。为此,我假设我们正在使用对象。现在,我们将需要检查已知范围内的质数和非质数对象的有效结果。那是你的出发点。
基本上,看一下函数,方法,程序或者脚本应该发生什么,然后再看同一代码绝对不应该发生什么。这就是测试的基础。当我们对代码应该发生的事情有了更多的了解时,只要准备好修改测试即可。
回答
另一个规范的答案。我相信Ron Jeffries的话:
Only test the code that you want to work.
回答
编写没有价值的代码总是一个坏主意。由于建议的测试不会给项目增加任何价值(或者非常接近实际价值)。这样一来,我们就浪费了宝贵的时间,可以花时间编写实际上可以带来价值的代码。
回答
这使我进入了单元测试,这让我感到非常高兴
我们才刚刚开始进行单元测试。
很长一段时间以来,我都知道开始这样做会很好,但是我不知道如何开始,更重要的是不知道要测试什么。
然后,我们不得不在会计程序中重写重要的一段代码。
这部分非常复杂,因为它涉及许多不同的场景。
我正在谈论的部分是一种支付已经输入到会计系统中的销售和/或者购买发票的方法。
我只是不知道如何开始编码,因为有很多不同的付款方式。
一张发票可能是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); }
回答
以下是我为测试付款方式而创建的一些测试。
段落数量不匹配