C#中实现单元测试的示例流程_MSTest测试项目

news2025/4/6 6:40:07

一、单元测试简介

1.1、单元测试简介

        在《单元测试艺术》一书中对于单元测试的定义是:【一个单元测试是一段代码,这段代码调用一个工作单元(指:调用软件中的一个方法,这个方法执行过程中所发生的所有行为以及最后产生的结果的总和),并检验该工作单元的一个具体的结果。如果关于这个结果的最终假设是错误的,单元测试就失败了;一个单元测试的范围可以小到一个方法,大到多个类

1.2、单元测试的意义

        要注意:引入单元测试或有单元测试并不能证明代码质量好(应根据项目情况进行调整适配,比如优先考虑核心模块),单元测试并不是越多越好(不要忽视引入代码测试以及无效的测试所带来的维护成本【如:增加开发的时间、增加人员成本】,毕竟资源是有限的)。

        若不考虑时间成本,还是希望各位可以为代码编写高效可靠的单元测试,毕竟这对提升代码质量有帮助;而单元测试具有如下意义:

1、通过单元测试用例确保的功能,不会在后续的迭代过程中产生Bug;

2、在重构模块时,因为有单元测试覆盖,也可以大胆去做;

3、可以通过单元测试模块了解模块具体功能和预期;

4、提高代码质量,降低耦合;

二、单元测试示例流程

2.1、打开VisualStudio创建项目

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; } 
        }

        /// <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、运行该银行账户类

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。

2.2、创建单元测试项目

2.2.1、新增单元测试项目,并且添加项目引用

 

2.2.2、创建单元测试类BankAccountTest.cs

将默认的UnitTest1.cs类修改名称为【BankAccountTest.cs】,默认的测试类示例如下

using Microsoft.VisualStudio.TestTools.UnitTesting;

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

其中【TestClass】标识该类是一个单元测试类;【TestMethod】标识该方法是一个单元测试方法。

2.2.3、编写一个单元测试的示例方法

单元测试方法的必备要求
序号
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银行账户的完整单元测试类内容如下:

具体的每步细节可以查看如下的链接

C# 单元测试教程 - Visual Studio (Windows) | Microsoft Learn了解如何使用托管代码的 Microsoft 单元测试框架和 Visual Studio 测试资源管理器创建、运行和自定义单元测试系列。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
}

三、单元测试资料

Visual Studio 中的测试工具 - Visual Studio (Windows) | Microsoft Learn了解如何使用功能 Visual Studio 来测试代码。icon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/visualstudio/test/?view=vs-2022

单元测试入门 - Visual Studio (Windows) | Microsoft Learn使用 Visual Studio 定义和运行单元测试,使代码保持正常运行并在客户之前找到错误和缺陷。icon-default.png?t=N7T8https://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 测试资源管理器如何提供灵活而高效的方法来运行单元测试并查看其结果。icon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/visualstudio/test/unit-test-basics?view=vs-2022#write-your-testsC# 单元测试教程 - Visual Studio (Windows) | Microsoft Learn了解如何使用托管代码的 Microsoft 单元测试框架和 Visual Studio 测试资源管理器创建、运行和自定义单元测试系列。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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1037300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

你好,python!——python基础中的基础

〇、注释 0.1 单行注释 python中用 “# ”来给某一行注释&#xff0c; 他的快捷键是 CTRL / &#xff0c;当注释内容已被注释时&#xff0c;会解除注释 0.2 多行注释 python中用 """ 注释内容 """ 来对内容进行多行注释 ""&q…

DATE和LocalDateTime在Java中有什么区别

在Java中&#xff0c;Date和LocalDateTime是两个表示日期和时间的类&#xff0c;它们有以下区别&#xff1a; 类型&#xff1a;Date是Java旧版提供的日期和时间类&#xff0c;而LocalDateTime是Java 8引入的新日期和时间API中的类。 不可变性&#xff1a;Date是可变类&#x…

Questasim与Visualizer的livesim仿真

1.前言 Live-Simulation (live-sim)模式允许Visualizer调试环境与Questasim进行交互操作&#xff0c;此模式将Visualizer GUI与Questasim仿真相结合&#xff0c;因此可以在线调试当前仿真的结果和波形。在livesim模式中&#xff0c;可以在Visualizer GUI中控制仿真、设置断点(…

【Java 基础篇】Java 类加载器详解

在Java编程中&#xff0c;类加载器&#xff08;Class Loader&#xff09;是一个重要的概念&#xff0c;它负责将类加载到Java虚拟机中&#xff0c;使程序能够正常运行。本文将详细解释Java类加载器的工作原理、不同类型的类加载器以及如何自定义类加载器。 什么是类加载器&…

智慧公厕以数字技术为中心,融合数字管理技术与城市治理需求精准对接

现代城市的发展离不开科技的进步&#xff0c;智慧公厕作为城市治理的重要方面之一&#xff0c;正逐渐受到越来越多的重视。智慧公厕以数字技术为中心&#xff0c;融合数字管理技术与城市治理需求精准对接&#xff0c;为城市居民提供更加方便、舒适的公共卫生设施。本文将以智慧…

2023年7月嵌入式项目开发专题总汇

一、前言 当前文章总结了28个基于不同单片机&#xff08;51单片机和STM32&#xff09;设计的软件和硬件项目。这些项目涵盖了计算器、手机、酒精检测仪、环境温度与湿度检测设备、考勤系统、门禁照相机、人体健康检测仪、数码相册、太阳能热水器、数显热水器和智能空调等多个领…

高性能计算环境下的深度学习异构集群建设与优化实践

★深度学习&#xff1b;模式识别&#xff1b;图像处理&#xff1b;人工智能建模&#xff1b;人工智能&#xff1b;深度学习算法&#xff1b;强化学习&#xff1b;神经网络&#xff1b;卷积神经网络&#xff1b;人工神经网络&#xff1b;VIBE算法&#xff1b;控制系统仿真&#…

用Redis做数据排名

1.背景 用Redis做数据缓存用的比较多&#xff0c;大家都能熟练使用String和Hash结构去存储数据&#xff0c;今天讲下如何使用ZSet来做数据排名。 假设场景是需要按天存储全国城市的得分数据&#xff0c;可以查询前十名的城市排名。 这个case可以使用传统关系型数据库做…

5-2 Pytorch中的模型层layers

深度学习模型一般由各种模型层组合而成。 torch.nn中内置了非常丰富的各种模型层。它们都属于nn.Module的子类&#xff0c;具备参数管理功能。 例如&#xff1a; nn.Linear, nn.Flatten, nn.Dropout, nn.BatchNorm2d, nn.Embedding nn.Conv2d,nn.AvgPool2d,nn.Conv1d,nn.ConvTr…

2023华为杯数学建模D题第三问-碳排放路径优化(能源消费结构调整的多目标优化模型构建详细过程+模型假设(可复制))

1.碳排放约束下&#xff08;人为干预按时碳达峰与碳中和的基准情景&#xff09;能源消费结构多目标优化模型构建 1.1基本假设 本文的模型设计主要基于以下几个基本假设&#xff1a; &#xff08;1&#xff09;能源消费结构调整的根本驱动要素&#xff0c;是对投资耗费的最小化…

uni-app图片保存到手机

uni.saveImageToPhotosAlbum(OBJECT) 官网地址uni.chooseImage(OBJECT) | uni-app官网 注意filePath不能是网络图片路径&#xff0c;如果我们是网络图片路径需要先下载文件资源到本地&#xff0c;然后再保存到相册 // 保存图片到手机 const saveImg () > {//wx.downloadF…

《计算机视觉中的多视图几何》笔记(10)

10 3D Reconstruction of Cameras and Structure 本章主要描述了如何利用2张图片来恢复相机的参数以及物体在三维空间中的形状。 文章目录 10 3D Reconstruction of Cameras and Structure10.1 Outline of reconstruction method10.2 Reconstruction ambiguity10.3 The proje…

【 Ubuntu】systemd服务创建、启用、状态查询、自启等

要在 Ubuntu 启动后执行一个守护脚本&#xff0c;您可以使用 Shell 脚本编写一个 systemd 服务单元。systemd 是 Ubuntu 中常用的服务管理工具&#xff0c;可以在系统启动时自动启动和管理服务。 下面是一个示例的守护脚本和 systemd 服务单元的步骤&#xff1a; 创建守护脚本…

机器学习第十课--提升树

一.Bagging与Boosting的区别 在上一章里我们学习了一个集成模型叫作随机森林&#xff0c;而且也了解到随机森林属于Bagging的成员。本节我们重点来学习一下另外一种集成模型叫作Boosting。首先回顾一下什么叫Bagging? 比如在随机森林里&#xff0c;针对于样本数据&#xff0c;…

实在智能入选中国信通院最新《高质量数字化转型产品及服务全景图》,领跑AI大模型核心领域

随着中国数字经济进入快速发展期&#xff0c;中国信通院于2021年正式发起“铸基计划-高质量数字化转型行动”&#xff0c;围绕企业数字化转型供需两侧引导行业数字化建设。近期&#xff0c;中国信通院发布了最新的2023《高质量数字化转型产品及服务全景图&#xff08;8月份新增…

苹果电脑如何卸载录屏软件音频驱动?

当你不想用录屏软件&#xff0c;且卸载后音频驱动还在&#xff0c;那想把音频驱动卸载的话应该如何操作&#xff1f; 或者是您使用录屏软件录制的视频没声音&#xff0c;应该如何操作&#xff0c;是音频插件的问题吗?需要将音频驱动卸载再重新安装吗&#xff1f; 接下来&…

文心一言 VS 讯飞星火 VS chatgpt (99)-- 算法导论9.3 5题

五、用go语言&#xff0c;假设你已经有了一个最坏情况下是线性时间的用于求解中位数的“黑箱”子程序。设计一个能在线性时间内解决任意顺序统计量的选择问题算法。 文心一言&#xff1a; 为了在线性时间内解决任意顺序统计量的选择问题&#xff0c;我们可以使用一个基于快速…

【51单片机】7-LED点阵

1.LED点阵简介 1.什么是LED点阵 1.外观 2.作用 3.内部构造原理图 点阵的优势&#xff1a;就是88个IO口独立控制8*8个LED灯亮灭 2.如何驱动LED点阵--74HC595 2.1 单片机端口直接驱动 要驱动8*8的点阵需要2个IO端口&#xff0c;&#xff08;16个IO口&#xff09;&#xff0c;…

【Verilog 教程】5.3Verilog带参数实例化

关键词&#xff1a; defparam&#xff0c;参数&#xff0c;例化&#xff0c;ram 当一个模块被另一个模块引用例化时&#xff0c;高层模块可以对低层模块的参数值进行改写。这样就允许在编译时将不同的参数传递给多个相同名字的模块&#xff0c;而不用单独为只有参数不同的多个模…

【RT】什么是对抗攻击 | 视觉跟踪

现在有机会接触一下针对深度学习神经网络的对抗攻击&#xff0c;并做整理如下&#x1f914; 对于CV攻防&#xff0c;其实去年12月组会听完就浏览过相关文章&#x1f449;面向目标检测的对抗样本综述后门防御, NIPS2022 adversarial attack for tracking CVPR2021 | IoU Attack导…