前言

单元测试是好, 但是也很花时间. 有些功能封装好了以后也不怎么会再打开, 所以通常就是徒手测试一下, 过了就过了.

但是往往就是那么神奇, 就是会有需求漏掉. 后来要加, 又由于不想潜水, 对自己有信心就会直接改, 然后只测试自己认为影响的部分. 最后就是有 bug.

所以要解决这种问题, 还是写测试比较好,毕竟一个 live 的项目出 bug 有时候会死的很难看.

3 选 1

NUnit vs XUnit vs MSTest

其实也没什么好选的, NUnit 是 XUnit 的前生, XUnit 是面向 .NET Core 的.

MSTest 是微软自带的, 但是很少人用.

微软官网三个教程都有. 所以自然是选 XUnit 咯.

文档参考

Unit Testing | .NET Core 101 [7 of 8] (YouTube 教程)

单元测试入门

dotnet test (.NET CLI)

dotnet test --filter (.NET CLI)

.NET Core Test Explorer (VS Code Plugin)

Testing .NET Core Apps with Visual Studio Code

大致长相

测试通常是开多一个 Project 做. Naming convention 和 Folder structure 我还没有一个规范, 之后再补上.

IDE 都支持的挺好的. 记得要开启 CodeLens 哦.

VS Code

Visual Studio

.NET CLI

定义 Trait 来做 filter

[Fact]
[Trait("Category", "Now")]

cmd

dotnet test --filter Category=Now
dotnet test --filter Category!=Now

moq

moq 在 TDD 扮演很重要的角色.

参考: moq4 Quickstart

安装: dotnet add package Moq

Service 通常会有许多依赖, 我们在做单侧时不能直接使用这些依赖, 不然依赖有问题就会被误以为是这个 Service 的问题.

所有依赖的 Service 都需要使用 mock 形式.

比如: Options

var options = new RImageOptions
{
Ratios = new List<Ratio> {
new Ratio { Wide = 16, High = 9 },
new Ratio { Wide = 3, High = 2 },
new Ratio { Wide = 4, High = 3 },
new Ratio { Wide = 1, High = 1 },
new Ratio { Wide = 3, High = 4 },
new Ratio { Wide = 2, High = 3 },
}
};
var snapshot = new Mock<IOptionsSnapshot<RImageOptions>>();
snapshot.Setup(s => s.Value).Returns(options);
mainService(snapshot.Object);

常见 Error: "may not be used in setup / verification expressions", 参考这里

原理: Mock<Interface> 它会做一个符合这个 interface 的对象, Mock<Class> 它会 inherit 这个 class 并且 override 它的 method,所以这个 class method 必须是 virtual / abstract 才能被 override

override 失败就报错了。所以呢,最好还是用 Interface。

除了上面这种直接依赖, 也有一种情况是, 我们在测试一个方法 A, 它会执行传入的委托/对象.

而我们想确认它是否真的执行了, 或者执行了几次, 执行时传入的参数是否真确等.

要测试的方法

public static void MatchBracket(string value, string bracket, Action<(int start, int end, string valueInBracket)> action) {
action((0, 2, "a"));
action((4, 6, "b"));
action((8, 14, "c {d}"));
}

检查委托执行顺序, 参数, 调用次数

[Fact]
[Trait("Category", "Now")]
public void MatchBracket()
{
var action = new Mock<Action<(int, int, string)>>(MockBehavior.Strict);
var sequence = new MockSequence();
action.InSequence(sequence).Setup(a => a(Tuple.Create(0, 2, "a").ToValueTuple())); // 确保顺序和参数
action.InSequence(sequence).Setup(a => a(Tuple.Create(4, 6, "b").ToValueTuple()));
action.InSequence(sequence).Setup(a => a(Tuple.Create(8, 14, "c {d}").ToValueTuple()));
Program.MatchBracket("{a} {b} {c {d}}", "{}", action.Object);
action.Verify(a => a(It.IsAny<(int, int, string)>()), Times.Exactly(3)); // 确保调用次数
}

更多 pattern 参考 Quickstart 就可以了.  比如 callbacks 也很好用.

Mocking Extension Methods

参考: Mocking Extension Methods

它没有优雅的实现方法,只有 workaround。不过视乎大家都不介意。

首先,我们有一个 string extension method ToTitleCase,有一个 Person.SayHello 类和方法,这个方法里面调用了 ToTitleCase。

public static class StringExtensions
{
public static string ToTitleCase(this string stringValue)
{
return stringValue;
}
} public class Person
{
public string SayHello(string name)
{
return $"Hi, {name.ToTitleCase()}";
}
}

现在我们要测试 Person.SayHello,但是又不希望 ToTitleCase 搞鬼,所以需要 mock 这个 ToTitleCase。

测试代码

public class Person_Test
{
[Fact]
public void Do()
{
var person = new Person();
var result = person.SayHello("derrick");
Assert.Equal("Hi, Derrick", result);
}
}

由于 ToTitleCase 有 Bug,所以测试失败了。

但其实我们想测试的是 SayHello,SayHello 的实现并没有 Bug,它是被 ToTitleCase 陷害了。

让我们来 Mock ToTitleCase,首先搞一个中间人。

public interface IStringExtensionsImplement
{
string ToTitleCase(string Value);
} public class StringExtensionsImplement : IStringExtensionsImplement
{
public string ToTitleCase(string stringValue)
{
return stringValue;
}
}

StringExtensionsImplement 负责具体的实现。

而 StringExtensions 只是一个简单的 wrapper 而已。

public static class StringExtensions
{
public static IStringExtensionsImplement Implement { get; set; } = new StringExtensionsImplement(); // 这里让测试可以替换 Mock Implement 进来
public static string ToTitleCase(this string stringValue)
{
return Implement.ToTitleCase(stringValue); // 具体依靠 Implement 对象
}
}

Implement 对象就是中间人。测试的时候我们就可以 Mock 这个对象,然后替换掉 StringExtensions.Implement。

测试代码

public class Person_Test
{
[Fact]
public void Do()
{
var mockImplement = new Mock<IStringExtensionsImplement>();
mockImplement.Setup(e => e.ToTitleCase(It.IsAny<string>())).Returns("Derrick"); // Mock and setup return
var originalImplement = StringExtensions.Implement;
StringExtensions.Implement = mockImplement.Object; // 偷龙转风 var person = new Person();
var result = person.SayHello("derrick");
Assert.Equal("Hi, Derrick", result); StringExtensions.Implement = originalImplement; // 替换回去
}
}

看懂了吗?其原理就是搞一个中间人来做实现,然后 mock 这个中间人再偷龙转风。

ASP.NET Core 单元测试的更多相关文章

  1. 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生

    [转].NET(C#):浅谈程序集清单资源和RESX资源   目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...

  2. Asp.Net Core 单元测试正确姿势

    背景 ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式,并且默认注入了很多服务,具体可以参考 官方文档, 相信只要使用过依赖注入框架的同学,都会对此有不同深入的理解,在此无需赘言. ...

  3. 关于单元测试的思考--Asp.Net Core单元测试最佳实践

    在我们码字过程中,单元测试是必不可少的.但在从业过程中,很多开发者却对单元测试望而却步.有些时候并不是不想写,而是常常会碰到下面这些问题,让开发者放下了码字的脚步: 这个类初始数据太麻烦,你看:new ...

  4. 使用 xUnit 编写 ASP.NET Core 单元测试

    还记得 .NET Framework 的 ASP.NET WebForm 吗?那个年代如果要在 Web 层做单元测试简直就是灾难啊..NET Core 吸取教训,在设计上考虑到了可测试性,就连 ASP ...

  5. ASP.NET CORE 中用单元测试测试控制器

    之前用ASP.NET CORE做的项目 加了一个新功能,数据库加了个字段balabala.... 更新到服务器上,新功能测试正常,然后就没管了..... 今天客户说网站有BUG,某个页面打开后出错了, ...

  6. K8S+GitLab-自动化分布式部署ASP.NET Core(一) 部署环境

    一.部署流程介绍 开发人员通过Git上传asp.net core 项目到Gilab,并编写好.gitlab-ci.yml , GitLab-Runner 自动拉取代码,然后进行Build,编译,单元测 ...

  7. 使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(九)-- 单元测试

    本篇将结合这个系列的例子的基础上演示在Asp.Net Core里如何使用XUnit结合Moq进行单元测试,同时对整个项目进行集成测试. 第一部分.XUnit 修改 Project.json 文件内容, ...

  8. ASP.NET Core中如何针对一个使用HttpClient对象的类编写单元测试

    原文地址: How to unit test a class that consumes an HttpClient with IHttpClientFactory in ASP.NET Core? ...

  9. 使用 xUnit 编写 ASP.NET Core WebAPI单元测试

    本文使用xUnit对ASP.NET Core WebAPI做单元测试,使用HttpClient的同步和异步请求,下面详细介绍xUnit的使用过程: 一.创建示例项目 模板为我们自动创建了一个Value ...

  10. ASP.NET Core中使用xUnit进行单元测试

    单元测试的功能自从MVC的第一个版本诞生的时候,就是作为一个重要的卖点来介绍的,通常在拿MVC与webform比较的时候,单元测试就是必杀底牌,把webform碾压得一无是处. 单元测试的重要性不用多 ...

随机推荐

  1. 2024秋招西山居游戏开发SEED种子实习笔试题

    西山居游戏开发SEED种子实习 2024年秋招笔试题目,仅供参考,请大佬多多指教 选择题 逆波兰数,TCP,操作系统FIFO,C语言大小端 填空题 一道LUA脚本写结果,一道并发存储优化题,计算机系统 ...

  2. 如何去除字符串中的 "\n" ?80% 的同学错了!

    大家好,我是鱼皮,今天分享一个小知识. 我最近负责的工作是设计一个 SQL 解析引擎.简单来说,就是将一个 SQL 表达式字符串,解析为一颗对象树,从而执行查询等一系列操作. 在最开始,我就遇到了一个 ...

  3. 产品探秘:智影AI——你的创意视频制作神器!

    只需3步,把小说变成视频.免费试用,首次注册赠送600积分. https://icomicai.com/ 在这个快节奏的时代,创意与效率并重成为了我们追求的新风尚.今天,就让我带你一起揭秘一款颠覆传统 ...

  4. [oeasy]python0091_仙童公司_八叛逆_intel_8080_altair8800_牛郎星

    编码进化 个人电脑 计算机 通过电话网络 进行连接 极客 利用技术 做一些有趣的尝试 极客文化 是 认真研究技术的 文化 计算机 不再是 高校和研究机构高墙里面的 神秘事物 而是 生活中常见的 家用电 ...

  5. 题解:AT_abc359_e [ABC359E] Water Tank

    背景 中考结束了,但是暑假只有一天,这就是我现在能在机房里面写题解的原因-- 分析 这道题就是个单调栈. 题目上问你第一滴水流到每个位置的时间.我们考虑,答案其实就是比当前木板高且距离当前木板最近的那 ...

  6. P10245 Swimming Pool题解

    P10245 Swimming Pool 题意 给你四条边 \(abcd\),求这四条边是否可以组成梯形. 思路 这显然是一道简单的普通数学题. 判断是否能构成梯形只需看四条边是否能满足,上底减下底的 ...

  7. vue项目锚点定位+滚动定位

    功能: HTML: js: scrollEvent(e) { let scrollItems = document.querySelectorAll('.condition-container') f ...

  8. 【VMware VCF】VMware Cloud Foundation Part 03:准备 Excel 参数表。

    VMware Cloud Foundation 使用 VMware Cloud Builder 工具完成自动化以及标准化的部署,除了要准备必须的用于部署管理域并运行管理相关组件的 ESXi 主机以外, ...

  9. ThinkPHP一对一关联模型的运用(ORM)

    一.序言 最近在写ThinkPHP关联模型的时候一些用法总忘,我就想通过写博客的方式复习和整理下一些用法. 具体版本: topthink/framework:6.1.4 topthink/think- ...

  10. C++命名空间、标准输入输出、引用

    1.简述C++中命名空间的作用. 答:避免重复定义全局变量的问题. 2.定义两个命名空间A 和 B 分别在A中和B中定义变量value.在main函数中将两个空间的value打印出来. #includ ...