使用C#,我需要一个名为User
具有用户名,密码,活动标志,名字,姓氏,全名等的类.
应该有方法来验证和保存用户.我只是为这些方法编写测试吗?我甚至需要担心测试属性,因为它们是.Net的getter和setter?
对此有很多好的回应也是我的问题:" 开始TDD - 挑战?解决方案?建议? "
我还建议看看我的博客文章(部分灵感来自我的问题),我得到了一些很好的反馈.即:
我不知道从哪里开始?
重新开始.在编写新代码时,只考虑编写测试.这可以重新处理旧代码或全新功能.
从简单开始.不要试图让你的头围绕测试框架以及TDD-esque.Debug.Assert工作正常.用它作为起点.它不会弄乱您的项目或创建依赖项.
开始积极.你正在努力提高你的技艺,感觉很好.我看到很多开发人员都乐于停滞不前,而不是尝试新事物来改善自己.你正在做正确的事,记住这一点,这将有助于阻止你放弃.
准备迎接挑战.开始进入测试非常困难.期待挑战,但请记住 - 挑战可以克服.
只测试您的期望
我刚开始时遇到了真正的问题,因为我经常坐在那里试图找出可能发生的每一个可能的问题,然后尝试测试并修复.这是一个令人头痛的快速方法.测试应该是真正的YAGNI过程.如果您知道存在问题,请为其编写测试.否则,不要打扰.
只测试一件事
每个测试用例应该只测试一件事.如果您发现自己将"和"放在测试用例名称中,那么您做错了什么.
我希望这意味着我们可以继续"吸气者和安装者":)
测试你的代码,而不是语言.
单元测试如:
Integer i = new Integer(7); assert (i.instanceOf(integer));
只有在编写编译器时才有用,并且instanceof
方法不可行的可能性非零.
不要测试可以依赖语言强制执行的东西.在您的情况下,我将专注于您的身份验证和保存方法 - 我会编写测试,确保他们可以优雅地处理任何或所有这些字段中的空值.
这让我进入单元测试,这让我很开心
我们刚刚开始进行单元测试.很长一段时间我都知道开始这样做会很好,但我不知道如何开始,更重要的是要测试什么.
然后我们不得不在会计程序中重写一段重要的代码.这部分非常复杂,因为它涉及很多不同的场景.我正在讨论的部分是支付已经输入会计系统的销售和/或购买发票的方法.
我只是不知道如何开始编码,因为有很多不同的付款方式.发票可能是100美元,但客户只转移了99美元.也许您已将销售发票发送给客户,但您也已从该客户处购买.所以你以300美元的价格卖掉了他,但是买了100美元.您可以期望您的客户支付200美元来结算余额.如果您以500美元的价格出售但客户只需支付250美元,该怎么办?
所以我有一个非常复杂的问题要解决,有很多可能性,一个场景可以完美地工作,但在其他类型的invocie /支付组合上会出错.
这是单元测试来救援的地方.
我开始编写(在测试代码中)一种方法来创建发票清单,包括销售和购买.然后我写了第二种方法来创建实际付款.通常,用户将通过用户界面输入该信息.
然后我创建了第一个TestMethod,测试一个非常简单的单一发票支付,没有任何付款折扣.当银行付款保存到数据库时,系统中的所有操作都会发生.如您所见,我创建了发票,创建了付款(银行交易)并将交易保存到磁盘.在我的断言中,我把正确的数字放在银行交易和链接的发票中.我在交易后检查付款数量,付款金额,折扣金额和发票余额.
测试运行后,我会去数据库并仔细检查我的预期是否存在.
在我编写测试之后,我开始编写支付方法(BankHeader类的一部分).在编码中我只用代码来打扰第一次测试.我还没有考虑其他更复杂的场景.
我运行了第一个测试,修复了一个小bug,直到我的测试通过.
然后我开始编写第二个测试,这次是支付折扣.在我编写测试后,我修改了付款方式以支持折扣.
在通过付款折扣测试正确性的同时,我还测试了简单付款.两个测试当然都应该通过.
然后我一直努力到更复杂的场景.
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(IListinvoices, int headerNumber, decimal amount, decimal discount) { ...... ...... ...... } [TestMethod] public void TestSingleSalesPaymentNoDiscount() { IList list = new List (); 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 list = new List (); 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 list = new List (); 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 list = new List (); 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 list = new List (); 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); }
如果他们真的很琐碎,那就不要费心去测试了.例如,如果它们是这样实现的;
public class User { public string Username { get; set; } public string Password { get; set; } }
另一方面,如果你正在做一些聪明的事情(比如在getter/setter中加密和解密密码)那么就给它一个测试.
规则是你必须测试你编写的每一个逻辑.如果你在getter和setter中实现了一些特定的功能,我认为它们值得测试.如果他们只为某些私有字段分配值,请不要打扰.
这个问题似乎是一个问题,在哪里可以得出哪些方法得到测试,哪些方法没有.
用于价值分配的制定者和获取者已经创建了一致性和未来增长的想法,并预见到一段时间后,设定者/获取者可能演变成更复杂的操作.将这些方法的单元测试放在适当的位置是有意义的,也是为了保持一致性和未来的增长.
代码可靠性,尤其是在进行更改以添加附加功能时,是主要目标.我不知道有人因为在测试方法中包含了setter/getter而被解雇了,但是我确信有些人希望他们测试的方法最后他们知道或者可以回忆起简单的set/get包装但是没有更长的时间.
也许团队的另一个成员扩展了set/get方法,以包含现在需要测试但之后没有创建测试的逻辑.但是现在你的代码正在调用这些方法而你并不知道它们已经改变并且需要深入测试,而你在开发和QA中进行的测试不会触发缺陷,但是在发布的第一天真正的业务数据确实存在触发它.
这两位队友现在将讨论谁丢球并且未能在进行单位测试时进行单位测试,其中包括可能失败但未被单元测试覆盖的逻辑.如果测试是从第一天开始在简单的set/gets上实现的,那么最初编写set/gets的队友将有更容易的时间从这个干净中出来.
我的观点是,用单元测试覆盖所有方法的几分钟"浪费"时间,即使是微不足道的,也可以节省几天的头痛,以及业务损失/声誉和失去某人的工作.
事实上你用单元测试包装琐碎的方法可能会被那个初级队友看到,当他们将琐碎的方法改为非平凡的方法并提示他们更新测试时,现在没有人因为缺陷被包含而遇到麻烦从达到生产.
我们编码的方式以及可以从代码中看到的规则可以帮助其他人.