引言

在我们上一篇文章了解了单元测试的基本概念和用法之后,今天我们来聊一下 TDD(测试驱动开发)

测试驱动开发 (TDD)

测试驱动开发英文全称是Test Driven Development 简称 TDD。

根据 UncleBob 的 TDD 描述总结

我们先创建一个测试项目

直接在 VS 创建即可,可以参考上一篇文章的创建过程

The Three Laws of TDD.

  • You are not allowed to write any production code unless it is to make a failing unit test pass.
  • You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  • You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

这是本文的描述的三个 TDD 开发的原则,它确保了代码的质量和可维护性。

下面对这三条内容做详细的解释

  • 第一条规则指出 不允许编写任何的生产代码,除非是在让单元测试通过时。

    • 简单理解就是在编写任何实际的业务逻辑代码之前,必须先编写一个或者多个单元测试,这些单元测试因为没有实现所以会失败,有了失败的单元测试之后我们才可以去在生产代码中实现业务逻辑
  • 第二条规则指出 不允许编写比失败所需更多的单元测试代码;编译失败也是失败:

    • 这可以理解为 在编写单元测试时,应该只编写足够使测试失败的最小代码量。这样,可以立即知道新写的生产代码是否解决了问题。编译失败同样被视为测试失败,因为编译不通过意味着代码无法运行。

那一个我们上一章节的一个数学计算类的例子


namespace dotNetParadise_TDD.Test; public class MathCalculatorTests
{
[Fact]
public void Add_TwoNumbers_ReturnSum()
{
// Arrange
var calculator = new MathCalculator(); // Act
var result = calculator.Add(3, 5); // Assert
Assert.Equal(8, result);
}
}

因为我们没有 MathCalculator 这个类的实现所以,代码会编译失败。

在这个示例中,我们展示了如何编写一个简单的单元测试,测试 Calculator 类的 Add 方法是否能够正确地将两个数字相加并返回正确的结果。根据 TDD 原则,我们只编写了必要部分的代码来测试这个功能,并且在这个阶段测试应该会失败,因为 Add 方法还未实现。编译失败也会被视为测试失败,这强调了编写足够简洁和精确的单元测试的重要性,符合第二条准则。

  • 第三条规则指出 不允许编写比通过单个失败单元测试所需更多的生产代码

    可以理解为在编写生产代码时,只需编写足够让失败的单元测试通过的代码,而不是一次性编写完整的功能。这有助于保持代码的小步前进,并且每次更改都有明确的测试验证。

现在把MathCalculator类中增加一个参数*2 即翻倍的一个功能

第一步编写一个单元测试方法,

    [Fact]
public void DoubleNumber_WhenGivenSingleNumber_ReturnsDouble()
{
// Arrange
var calculator = new MathCalculator(); // Act
var result = calculator.DoubleNumber(2); // Assert
Assert.Equal(4, result);
}

第二步 编写足够让失败的单元测试通过的代码

namespace dotNetParadise_TDD.Test;

public class MathCalculator
{
public int DoubleNumber(int number)
{
throw new NotImplementedException();
}
}

接下来运行单元测试

 dotNetParadise_TDD.Test.MathCalculatorTests.DoubleNumber_WhenGivenSingleNumber_ReturnsDouble
 源: MathCalculatorTests.cs 行 20
 持续时间: 371 毫秒 消息: 
System.NotImplementedException : The method or operation is not implemented. 堆栈跟踪: 
MathCalculator.DoubleNumber(Int32 number) 行 7
MathCalculatorTests.DoubleNumber_WhenGivenSingleNumber_ReturnsDouble() 行 26
RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

结果和预期一样,测试没有成功

现在来重构一下这个方法

    public int DoubleNumber(int number)
{
//throw new NotImplementedException();
return 2 * number;
}

再次运行单元测试

可以看到单元测试成功了!

TDD 开发流程图

最后

通常我们进行单元测试的时候都是先写业务逻辑,然后再单元测试,当系统业务逻辑变复杂之后可能会遗漏一些测试 CaseTDD 的出现就是解决这个问题,通过测试 Case 来写重构业务代码的模式。

这三个规则确保了 TDD 的核心循环:红(测试失败)、绿(测试通过)、重构。通过不断地重复这个过程,开发者能够编写出高质量、可测试、易于维护的代码。

本文完整源代码

单元测试篇2-TDD三大法则解密的更多相关文章

  1. 持续集成之单元测试篇——WWH(讲讲我们做单元测试的故事)

    持续集成之单元测试篇--WWH(讲讲我们做单元测试的故事) 前言 临近上线的几天内非重大bug不敢进行发版修复,担心引起其它问题(摁下葫芦浮起瓢) 尽管我们如此小心,仍不能避免修改一些bug而引起更多 ...

  2. 看完这篇Redis缓存三大问题,保你面试能造火箭,工作能拧螺丝。

    前言 日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题. 一旦涉及大数据量的需求,如一些商品抢购的情景,或者主页访问量瞬间较 ...

  3. .net持续集成单元测试篇之单元测试简介以及在visual studio中配置Nunit使用环境

    系列目录 单元测试及测试驱动开发简介 什么是单元测试 单元测试是一段自动化的代码,这段代码调用被测试的工作单元,之后对这个单元的单个最终结果的某些假设进行检验.单元测试几乎都是用单元测试框架编写的.单 ...

  4. Spring Boot 入门之单元测试篇(五)

    博客地址:http://www.moonxy.com 一.前言 JUnit 是一个由 Java 语言编写的开源的回归测试(回归测试是指重复以前全部或部分的相同测试)框架,由Erich Gamma 和 ...

  5. .NET进阶篇04-Serialize序列化、加密解密

    知识需要不断积累.总结和沉淀,思考和写作是成长的催化剂这篇很轻松,没有什么费脑子的,所以解析较少,代码较多,为数不多的拿来即用篇整个章节分布请移步 内容目录 一.概述二.序列化1.二进制文件2.XML ...

  6. 单元测试篇----cppUnit的安装与使用

    在刚学习单元测试章节的时候,尝试着使用dev—c++来编译cppunit,但一直没成功,也尝试问过同学,一直没有很好的方法,因此浪费了不少时间.今天又耐心的尝式一下,意外成功了.以下是详细的安装步骤: ...

  7. Java学习第三篇:类的三大特征,抽象类,接口,final关键字

    一.类的三大特征 1.封装性 (1).什么是封装 封装就是把抽象出的数据和对数据的操作封装在一起, 数据被保护在内部, 程序的其他部分只有通过被授权的操作(成员方法), 才能对数据进行操作. (2). ...

  8. PHP 基础篇 - PHP 中 DES 加解密详解

    一.简介 DES 是对称性加密里面常见一种,全称为 Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法.密钥长度是64位(bit),超过位数密钥被忽略.所谓对 ...

  9. TDD三大定律

    You must write a failing unit test before you write production code. You must stop writing that unit ...

  10. 第三十篇 面向对象的三大特性之继承 supre()

    继承 一 .什么是继承? 类的继承跟现实生活中的父.子.孙子.重孙子的继承关系一样,父类又称基类. Python中类的继承分为:单继承 和  多继承. # 定义父类 class ParentClass ...

随机推荐

  1. Counter函数

    构造一个空Counter import collections c = collections.Counter() c.update('abcdaab') print(c) # Counter({'a ...

  2. 开源软件CVE_Search的配置与使用(详细)

    目录 项目介绍 环境配置 初始化cve_search 使用cve_search 简介 CVE: https://baike.baidu.com/item/CVE/9483464?fr=aladdin ...

  3. 旅游景点 Tourist Attractions (壮压 DP)题解

    简化题意 题目链接--不卡内存班 题目链接--卡内存版 给定 \(n\) 个点和 \(m\) 条边组成的无向图,按照一定限制要求停留 \(2\sim k+1\) 共 \(k\) 个点(可以经过但不停留 ...

  4. 【Azure 环境】如何解决Principal 2330xxxxxxxxxxxxxxxxxxxx31efc5 does not exist in the directory xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx问题

    问题描述 在使用 Key Vault 和 Azure CLI 管理存储帐户密钥的官方文档中,其中有一步是"向 Key Vault 授予对你的存储帐户的访问权限", 其中CLI命令中 ...

  5. 基于 Nebula Graph 构建百亿关系知识图谱实践

    本文首发于 Nebula Graph Community 公众号 一.项目背景 微澜是一款用于查询技术.行业.企业.科研机构.学科及其关系的知识图谱应用,其中包含着百亿级的关系和数十亿级的实体,为了使 ...

  6. [linux 爬坑] 几个linux发行版尝试和令人崩溃的ssr安装体验

    最近电脑上的manjaro好像出了问题,长时间不用就会死机.也懒得追究原因了,正好决定尝试几个发行版.首先尝试安装银河麒麟    这个发行版实际上就是ubuntu,甚至源什么的都是ubuntu的,也不 ...

  7. 虚拟机安装Mac操作系统

    参考博客https://www.bilibili.com/read/cv25662180/?spm_id_from=333.1007.0.0

  8. Java 常用类 String类与其他结构之间的转换-----String 与 byte[]之间的转换

    1 /** 2 * 3 * String 与 byte[]之间的转换 4 * 编码:String ---> byte[]:调用String的getBytes() 5 * 解码:byte[]--- ...

  9. Codeforces Round 928 (Div. 4)(A、B、C、D、E、G)

    目录 A B C D E G A 统计A.B输出 #include <bits/stdc++.h> #define int long long #define rep(i,a,b) for ...

  10. 【译】32位 .NET Framework 项目的 WinForm 设计器选择

    在客户反馈的推动下,Visual Studio 2022 向64位架构过渡,标志着增强开发体验的关键一步.正如 Klaus Loffelmann 在他的博客文章中所描述的那样,这种转换增强了整体性能和 ...