在《单元测试艺术》一书中对于单元测试的定义是:【一个单元测试是一段代码,这段代码调用一个工作单元(指:调用软件中的一个方法,这个方法执行过程中所发生的所有行为以及最后产生的结果的总和),并检验该工作单元的一个具体的结果。如果关于这个结果的最终假设是错误的,单元测试就失败了;一个单元测试的范围可以小到一个方法,大到多个类】
要注意:引入单元测试或有单元测试并不能证明代码质量好(应根据项目情况进行调整适配,比如优先考虑核心模块),单元测试并不是越多越好(不要忽视引入代码测试以及无效的测试所带来的维护成本【如:增加开发的时间、增加人员成本】,毕竟资源是有限的)。
若不考虑时间成本,还是希望各位可以为代码编写高效可靠的单元测试,毕竟这对提升代码质量有帮助;而单元测试具有如下意义:
1、通过单元测试用例确保的功能,不会在后续的迭代过程中产生Bug;
2、在重构模块时,因为有单元测试覆盖,也可以大胆去做;
3、可以通过单元测试模块了解模块具体功能和预期;
4、提高代码质量,降低耦合;

1、使用VisualStudio创建一个跨平台的控制台项目(取名为:Test_UnitTest),如下图所示:



2、新建一个名为: 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; }
- }
-
- ///
- /// 借钱出去
- ///
- /// 金额
- ///
- 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";
-
- ///
- /// 借钱出去
- ///
- /// 金额
- ///
- 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}】");
- }
-
- ///
- /// 贷款进来
- ///
- /// 金额
- ///
- public void Credit(double amout)
- {
- if (amout<0)
- {
- throw new ArgumentOutOfRangeException("amout");
- }
- m_balance += amout;
- Console.WriteLine($"贷款【{amout}】进来后的余额是【{Balance}】");
- }
-
-
- }//Class_end
- }
3、运行该银行账户类
- 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();
- }
-
-
- }
- }

到目前为止程序是没有报错的;但是仔细查看我们就会发现一个问题,借钱出去后,我们的银行账户应该是余额减少的;但是我们的程序却显示余额增加了,这明显是错误的;关于这样的类似错误我们可以通过单元测试来避免,修复这个Bug。





将默认的UnitTest1.cs类修改名称为【BankAccountTest.cs】,默认的测试类示例如下
- using Microsoft.VisualStudio.TestTools.UnitTesting;
-
- namespace BankTests
- {
- [TestClass]
- public class BankAccountTests
- {
- [TestMethod]
- public void TestMethod1()
- {
- }
- }
- }
其中【TestClass】标识该类是一个单元测试类;【TestMethod】标识该方法是一个单元测试方法。
| 序号 | |
| 1 | 必须使用 [TestMethod] 特性进行修饰 |
| 2 | 方法必须返回 void |
| 3 | 单元测试方法必须不能含有参数 |
Assert.AreEqual、Assert.IsTrue 等方法经常用于单元测试Assert Class (Microsoft.VisualStudio.TestTools.UnitTesting) | Microsoft Learn | |
①分析:编写单元测试方法以验证 BankAccount 类的 Debit 方法的行为,则至少需要检查三种行为:
如果借方金额大于余额,该方法将引发 ArgumentOutOfRangeException 。
如果借方金额小于零,该方法会引发 ArgumentOutOfRangeException。
如果借方金额有效,该方法会从帐户余额中减去该借方金额。
②第一个单元测试方法:验证是否从帐户中提取了正确的有效金额(即小于帐户余额且大于零);将以下方法添加到该 BankAccountTests 类:
- [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, "该账户的金额并没有正确的借出去!!!");
- }
③生成并运行单元测试和解决错误
首先生成解决方案

然后选择顶部的【测试】-->【运行所有测试】结果如下:


最后根据单元测试的错误提示排查是由于调用账户的借出金额后的余额与期望的余额不一致导致报错,我们需要排查【account.Debit()】方法,需要将【account.Debit()】方法的 m_balance += amout;修改为 m_balance -= amout;后保存重新【运行所有测试】即可通过单元测试),如下图所示:


BankAccountTest.cs银行账户的完整单元测试类内容如下:
具体的每步细节可以查看如下的链接
- 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
(() => 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
(() => 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
- }
单元测试入门 - Visual Studio (Windows) | Microsoft Learn使用 Visual Studio 定义和运行单元测试,使代码保持正常运行并在客户之前找到错误和缺陷。
https://learn.microsoft.com/zh-cn/visualstudio/test/getting-started-with-unit-testing?view=vs-2022&tabs=dotnet%2Cmstest 单元测试基础知识 - Visual Studio (Windows) | Microsoft Learn了解 Visual Studio 测试资源管理器如何提供灵活而高效的方法来运行单元测试并查看其结果。
https://learn.microsoft.com/zh-cn/visualstudio/test/unit-test-basics?view=vs-2022#write-your-testsC# 单元测试教程 - Visual Studio (Windows) | Microsoft Learn了解如何使用托管代码的 Microsoft 单元测试框架和 Visual Studio 测试资源管理器创建、运行和自定义单元测试系列。
https://learn.microsoft.com/zh-cn/visualstudio/test/walkthrough-creating-and-running-unit-tests-for-managed-code?view=vs-2022