Example process for implementing unit testing in C#_MSTest test project

1. Introduction to unit testing

1.1. Introduction to unit testing

The definition of unit testing         in the book " The Art of Unit Testing " is: [ A unit test is a piece of code that calls a unit of work (referring to: calling a method in the software, and everything that happens during the execution of this method behavior and the sum of the final results) , and examine a specific result of the unit of work. If the final assumption about the result is wrong, the unit test fails; the scope of a unit test can be as small as a method or as large as multiple classes ]

1.2. The significance of unit testing

        It should be noted that the introduction of unit tests or the presence of unit tests does not prove that the code quality is good (it should be adjusted according to the project situation, such as giving priority to core modules). The more unit tests are not the better (don’t ignore the introduction of code tests and ineffective ones). Maintenance costs caused by testing [such as increased development time, increased personnel costs], after all, resources are limited).

        Regardless of the time cost, I still hope that you can write efficient and reliable unit tests for the code. After all, this will help improve the quality of the code; and unit tests have the following meanings:

1. Functions ensured through unit test cases will not cause bugs in subsequent iterations;

2. When refactoring the module, you can do it boldly because it is covered by unit tests;

3. You can understand the specific functions and expectations of the module through the unit test module;

4. Improve code quality and reduce coupling;

2. Unit testing example process

2.1. Open Visual Studio to create a project

1. Use VisualStudio to create a cross-platform console project (named: Test_UnitTest), as shown in the following figure:

2. Create a new bank account class named: BankAccount

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace test_UnitTest
{
    public class BankAccount
    {
        private readonly string m_customerName;
        private double m_balance;

        public BankAccount(string customerName,double balance)
        {
            m_customerName = customerName;
            m_balance = balance;

            Console.WriteLine($"\n初始的余额是【{Balance}】");
        }

        public string CustomerName
        {
            get { return m_customerName; }
        }

        public double Balance
        {
            get { return m_balance; } 
        }

        /// <summary>
        /// 借钱出去
        /// </summary>
        /// <param name="amout">金额</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public void Debit(double amout)
        {
            if (amout>m_balance)
            {
                throw new ArgumentOutOfRangeException("amount");
            }
            if (amout<0)
            {
                throw new ArgumentOutOfRangeException("amount");
            }
            m_balance += amout;
            Console.WriteLine($"借钱【{amout}】出去后的余额是【{Balance}】");
        }

        public const string DebitAmountExceedsBalanceMessage = "当前借出的金额超过当前的余额";
        public const string DebitAmountLessThanZeroMessage = "当前借出的金额小于0";

        /// <summary>
        /// 借钱出去
        /// </summary>
        /// <param name="amout">金额</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public void Debit2(double amout)
        {
            if (amout > m_balance)
            {
                throw new ArgumentOutOfRangeException("amount",amout, DebitAmountExceedsBalanceMessage);
            }
            if (amout < 0)
            {
                throw new ArgumentOutOfRangeException("amount",amout, DebitAmountLessThanZeroMessage);
            }
            m_balance += amout;
            Console.WriteLine($"借钱【{amout}】出去后的余额是【{Balance}】");
        }

        /// <summary>
        /// 贷款进来
        /// </summary>
        /// <param name="amout">金额</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public void Credit(double amout)
        {
            if (amout<0) 
            {
                throw new ArgumentOutOfRangeException("amout");
            }
            m_balance += amout;
            Console.WriteLine($"贷款【{amout}】进来后的余额是【{Balance}】");
        }


    }//Class_end
}

3. Run the bank account class

namespace test_UnitTest
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            BankAccount ba = new BankAccount("张三", 11.99);

            ba.Credit(5.77);
            ba.Debit(11.22);
            Console.WriteLine($"当前账户的余额是 ${ba.Balance}");

            Console.ReadLine();
        }


    }
}

        So far, the program has reported no errors; but if we look closely, we will find a problem. After borrowing money, the balance of our bank account should decrease; but our program shows that the balance has increased, which is obviously wrong; We can avoid similar errors like this through unit testing and fix this bug.

2.2. Create unit test project

2.2.1. Add a new unit test project and add a project reference

 

2.2.2. Create unit test class BankAccountTest.cs

Change the name of the default UnitTest1.cs class to [ BankAccountTest.cs ]. An example of the default test class is as follows

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BankTests
{
    [TestClass]
    public class BankAccountTests
    {
        [TestMethod]
        public void TestMethod1()
        {
        }
    }
}

Among them, [TestClass] identifies the class as a unit test class; [TestMethod] identifies the method as a unit test method.

2.2.3. Write a sample method for unit testing

Essential requirements for unit testing methods
serial number
1 Must be  [TestMethod] modified using attributes
2 Method must return void
3 Unit test methods must not contain parameters
 Assert.AreEqual, Assert.IsTrue  and other methods are often used in unit testing Assert Class (Microsoft.VisualStudio.TestTools.UnitTesting) | Microsoft Learn

① Analysis: When writing unit test methods to verify  the behavior of BankAccount class  Debit methods, at least three behaviors need to be checked:

  • If the debit amount is greater than the balance, this method throws  ArgumentOutOfRangeException  .

  • If the debit amount is less than zero, this method throws  ArgumentOutOfRangeException .

  • If the debit amount is valid, this method subtracts the debit amount from the account balance.

②First unit test method: Verify that the correct valid amount is withdrawn from the account (i.e. less than the account balance and greater than zero) ; add the following method to BankAccountTests this class

        [TestMethod]
        //验证借出指定的金额后是否与预期的金额相等
        public void Debit_WithValidAmount_UpdatesBalance()
        {

            double beginningBalance = 11.99;    //初始金额
            double debitAmount = 4.55;          //借出去的金额数量
            double expected = 7.44;             //进出金额后期望剩余的金额数量
            BankAccount account = new BankAccount("张三", beginningBalance);

            account.Debit(debitAmount);

            double actual = account.Balance;
            //判断是否相等[若不相等则抛出异常]
            Assert.AreEqual(expected, actual, 0.001, "该账户的金额并没有正确的借出去!!!");
        }

③ Generate and run unit tests and resolve errors

First generate the solution

Then select [Test] at the top-->[Run all tests] and the results are as follows: 

 Finally, according to the error prompt of the unit test, the error is reported because the balance after calling the loan amount of the account is inconsistent with the expected balance. We need to troubleshoot the [account.Debit()] method and need to change the [account.Debit()] method. m_balance += amout; change to m_balance -= amout; and then save and re-[Run all tests] to pass the unit test), as shown in the following figure:

The complete unit test class content of BankAccountTest.cs bank account is as follows:

Specific details of each step can be viewed at the following link

C# Unit Testing Tutorial - Visual Studio (Windows) | Microsoft Learn how to create, run, and customize a unit test series using the Microsoft unit testing framework for managed code and the Visual Studio Test Explorer. icon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/visualstudio/test/walkthrough-creating-and-running-unit-tests-for-managed-code?view=vs-2022

using test_UnitTest;

namespace UnitTest
{
    [TestClass]
    public class BankAccountTest
    {
        [TestMethod]
        //验证借出指定的金额后是否与预期的金额相等
        public void Debit_WithValidAmount_UpdatesBalance()
        {

            double beginningBalance = 11.99;    //初始金额
            double debitAmount = 4.55;          //借出去的金额数量
            double expected = 7.44;             //进出金额后期望剩余的金额数量
            BankAccount account = new BankAccount("张三", beginningBalance);

            account.Debit(debitAmount);

            double actual = account.Balance;
            //判断是否相等[若不相等则抛出异常](运行测试后发现抛出了【该账户的金额并没有正确的借出去!!!】的异常;我们此时需要排查【account.Debit()】方法,
            //需要将【account.Debit()】方法的 m_balance += amout;修改为 m_balance -= amout;后重新运行即可通过单元测试)
            Assert.AreEqual(expected, actual, 0.001, "该账户的金额并没有正确的借出去!!!");
        }

        [TestMethod]
        //验证借出去的金额小于零时的行为是否正确(即:应该报错)
        public void Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange()
        {
            double beginningBalance = 11.99;
            double debitAmount = -100;

            BankAccount bankAccount = new BankAccount("张三",beginningBalance);
            //使用 ThrowsException 方法断言已引发正确的异常。 除非 ArgumentOutOfRangeException 已引发,否则该方法将导致测试失败。
            //如果在借方金额小于零时,临时修改测试方法以引发更通用的 ApplicationException,则测试将正确运行,即测试将失败。
            Assert.ThrowsException<System.ArgumentOutOfRangeException>(() => bankAccount.Debit(debitAmount));


        }

        [TestMethod]
        //验证借出去的金额大于余额时的行为是否正确(即:应该报错)
        public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
        {
            double beginningBalance = 11.99;
            double debitAmount =20.0;

            BankAccount bankAccount = new BankAccount("张三", beginningBalance);
            //使用 ThrowsException 方法断言已引发正确的异常。 除非 ArgumentOutOfRangeException 已引发,否则该方法将导致测试失败。
            //如果在借方金额大于余额时,临时修改测试方法以引发更通用的 ApplicationException,则测试将正确运行,即测试将失败。
            Assert.ThrowsException<System.ArgumentOutOfRangeException>(() => bankAccount.Debit(debitAmount));
        }

        [TestMethod]
        //该测试方法解决【没有办法知道哪个条件(amount > m_balance 或 amount < 0)导致在测试期间引发异常。
        //我们只知道在方法中引发了一个 ArgumentOutOfRangeException。
        //更理想的情况是,如果我们知道是 BankAccount.Debit 中的哪个条件导致引发异常(amount > m_balance 或 amount < 0),
        //这样就可以确信我们的方法可以正确合理地检查其自变量。】
        public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange2()
        {
            // Arrange
            double beginningBalance = 11.99;
            double debitAmount = 20.0;
            BankAccount account = new BankAccount("张三", beginningBalance);

            // Act
            try
            {
                account.Debit2(debitAmount);
            }
            catch (System.ArgumentOutOfRangeException e)
            {
                // Assert
                StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
            }
        }

        [TestMethod]
        //该测试方法解决【测试方法不会处理它原本应该处理的所有情况。
        //如果所测试的方法 Debit 在 debitAmount 大于余额(或小于零)时未能引发 ArgumentOutOfRangeException,则该测试方法通过。
        //这样并不好,因为如果未引发异常,则希望测试方法失败。这是测试方法中的一个 bug。
        //要解决该问题,在测试方法末尾添加 Assert.Fail 断言,处理未引发异常的情况。】
        public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange3()
        {
            // Arrange
            double beginningBalance = 11.99;
            double debitAmount = 20.0;
            BankAccount account = new BankAccount("张三", beginningBalance);

            // Act
            try
            {
                account.Debit2(debitAmount);
            }
            catch (System.ArgumentOutOfRangeException e)
            {
                // Assert
                StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage);
                return;
            }

            Assert.Fail("未引发预期的异常");
        }


    }//Class_end
}

3. Unit test information

Testing Tools in Visual Studio - Visual Studio (Windows) | Microsoft Learn Learn how to use features in Visual Studio to test your code. icon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/visualstudio/test/?view=vs-2022

Get Started with Unit Testing - Visual Studio (Windows) | Microsoft Learn Use Visual Studio to define and run unit tests to keep your code running and find bugs and defects before your customers do. icon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/visualstudio/test/getting-started-with-unit-testing?view=vs-2022&tabs=dotnet%2Cmstest  Unit Testing Basics - Visual Studio (Windows) | Microsoft Learn Learn how Visual Studio Test Explorer provides a flexible and efficient way to run unit tests and view their results. icon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/visualstudio/test/unit-test-basics?view=vs-2022#write-your-tests C# Unit Test Tutorial - Visual Studio (Windows) | Microsoft Learn Learn How Create, run, and customize unit test series using the Microsoft Unit Testing Framework for managed code and Visual Studio Test Explorer. icon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/visualstudio/test/walkthrough-creating-and-running-unit-tests-for-managed-code?view=vs-2022

Assert Class (Microsoft.VisualStudio.TestTools.UnitTesting) | Microsoft LearnA collection of helper classes to test various conditions within unit tests. If the condition being tested is not met, an exception is thrown.icon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.visualstudio.testtools.unittesting.assert?view=visualstudiosdk-2022

Guess you like

Origin blog.csdn.net/xiaochenXIHUA/article/details/133234190