C# 编写单元测试时,您如何知道要测试什么?

C# 编写单元测试时,您如何知道要测试什么?,c#,unit-testing,tdd,C#,Unit Testing,Tdd,使用C#,我需要一个名为User的类,它有用户名、密码、活动标志、名字、姓氏、全名等 应该有验证和保存用户的方法。我只是为这些方法编写一个测试吗?我甚至需要担心测试属性,因为它们是.Net的getter和setter吗?规则是您必须测试您编写的每一条逻辑。如果您在getter和setter中实现了一些特定的功能,我认为它们值得测试。如果他们只给一些私有字段赋值,不用麻烦了 正如我在敏捷开发环境中理解的那样,Mike,是的,您需要测试getter和setter(假设它们是公开可见的)。单元测试的整

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


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

规则是您必须测试您编写的每一条逻辑。如果您在getter和setter中实现了一些特定的功能,我认为它们值得测试。如果他们只给一些私有字段赋值,不用麻烦了

正如我在敏捷开发环境中理解的那样,Mike,是的,您需要测试getter和setter(假设它们是公开可见的)。单元测试的整个概念是测试软件单元,在本例中,它是一个类,作为一个整体。由于getter和setter是外部可见的,因此您需要在进行身份验证和保存的同时对它们进行测试。

为getter和setter编写单元测试并没有坏处。现在,他们可能只是在幕后进行字段get/set,但将来可能需要测试验证逻辑或属性间依赖关系。当您正在考虑它时,现在编写它会更容易,如果有机会,记得对它进行改装。

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

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

单元测试,如:

Integer i = new Integer(7);
assert (i.instanceOf(integer));
仅当您正在编写编译器并且
instanceof
方法不起作用的可能性为非零时,此函数才有用


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

我将测试您的getter和setter。根据编写代码的人,有些人会更改getter/setter方法的含义。我已经看到变量初始化和其他验证是getter方法的一部分。为了测试这类东西,您需要单元测试明确地覆盖代码。

我个人会“测试任何可能破坏的东西”,简单的getter(甚至更好的自动属性)不会破坏。我从未有过一个简单的返回语句失败,因此也从未对它们进行过测试。如果getter中包含计算或其他形式的语句,我肯定会为它们添加测试


就我个人而言,我将其用作模拟对象框架,然后验证我的对象是否以应有的方式调用了周围的对象

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


对于同样适用的简单整数,如果传递long而不是integer,会发生什么?这就是您编写UT的原因:)

如果它们真的很琐碎,那么就不用麻烦测试了。例如,如果它们是这样实施的

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

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

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

您应该尽可能使用单元测试来测试“每一个非平凡的代码块”

如果您的属性很琐碎,并且不太可能有人在其中引入bug,那么不进行单元测试应该是安全的


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

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


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

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

事后编写测试要痛苦得多,但却是可行的

在你的位置上,我会这么做:

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

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


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

    不要测试明显工作(样板)
       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()
            {
    
            }
        }
    
    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);
        }