Bogus 实战:使用 Bogus 和 EFCore 生成模拟数据和种子数据【完整教程】
引言
上一章我们介绍了在xUnit
单元测试中用xUnit.DependencyInject
来使用依赖注入,上一章我们的Sample.Repository
仓储层有一个批量注入的接口没有做单元测试,今天用这个示例来演示一下如何用Bogus
创建模拟数据 ,和 EFCore
的种子数据生成
Bogus 的优势
丰富的数据生成支持:Bogus 提供了广泛的 API 支持,涵盖了各种数据类型和用例,使得生成虚假数据变得非常灵活和方便。
重复性和可控性:通过设置种子值,可以确保生成的虚假数据是可重复的,这对于需要一致的测试数据或示例数据非常有用。
易于使用:Bogus 使用流畅的语法和简单的方法调用,使得生成虚假数据变得简单直观,即使是对库不熟悉的用户也可以快速上手。
内置规则和语义:内置了许多常见数据类别的规则和语义,例如公司名称、产品名称、地址等,可以快速生成符合实际场景的数据。
灵活性:除了内置规则外,还可以通过自定义规则来生成特定的数据,满足不同场景下的需求。
社区支持:Bogus 是一个受欢迎的开源库,拥有活跃的社区支持和维护,可以获得持续的更新和改进。
Bogus 实战
简介
Bogus
是一个简单的.NET
语言(如 C#
、F#
和 VB.NET
)的假数据生成器。Bogus
本质上是 faker.js
的 C#
移植版本,并受到 FluentValidation
的语法糖的启发。
使用
创建新的xUnit
测试项目dotNetParadise.Bogus
Nuget
包安装Bogus
Install-Package Bogus
创建一个`Bogus`帮助类
```c#
using Bogus;
using Sample.Repository.Models;
namespace dotNetParadise.Bogus
{
public class BogusHelper
```package manager
PM> NuGet\Install-Package Bogus -Version 35.5.0
和上一篇的配置一样,测试项目需要添加仓储层的项目引用,并通过
Nuget
安装xUnit.DependencyInject
,配置Startup
。
先看一下我们的Staff
实体
public class Staff
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int? Age { get; set; }
public List<string>? Addresses { get; set; }
public DateTimeOffset? Created { get; set; }
}
接下来对我们批量新增的接口进行单元测试,测试数据通过Bogus
生成,先看使用在讲解用法。
生成500
条测试数据保存到 DB
[Fact]
public async Task BatchAddStaffAsync_WhenCalled_ShouldAddStaffToDatabase()
{
// Arrange
var staffs = new Faker<Staff>()
.RuleFor(u => u.Name, f => f.Person.FullName)
.RuleFor(u => u.Email, f => f.Person.Email)
.RuleFor(u => u.Age, f => f.Random.Number(18, 60))
.RuleFor(u => u.Addresses, f => f.MakeLazy(f.Random.Number(1, 3), () => f.Address.FullAddress()))
.RuleFor(u => u.Created, f => f.Date.PastOffset())
.Generate(500);
// Act
await _staffRepository.BatchAddStaffAsync(staffs, CancellationToken.None);
// Assert
var retrievedStaffs = await _staffRepository.GetAllStaffAsync(CancellationToken.None);
Assert.NotNull(retrievedStaffs); // 确保 Staff 已成功添加到数据库
Assert.Equal(500, retrievedStaffs.Count); // 确保正确数量的 Staff 已添加到数据库
Assert.True(staffs.All(x => retrievedStaffs.Any(_ => x.Id == _.Id)));
}
看代码配置跟FluentValidation
都是一样都是通过RuleFor
来配置实体的属性
看一下生成的测试数据
Run Tests
单元测试成功,有了Bogus
之后我们创建一些测试数据就方便多了
Bogus 的用法
locales 国际化
Bogus
支持许多不同的地区设置(locales
),这些地区设置可用于生成特定语言或地区的虚假数据。您可以通过设置不同的 locale
参数来使用不同的地区设置。
Bogus
支持以下地区设置(locales
)
Locale Code | Language | Locale Code | Language |
---|---|---|---|
af_ZA | Afrikaans | fr_CH | French (Switzerland) |
ar | Arabic | ge | Georgian |
az | Azerbaijani | hr | Hrvatski |
cz | Czech | id_ID | Indonesia |
de | German | it | Italian |
de_AT | German (Austria) | ja | Japanese |
de_CH | German (Switzerland) | ko | Korean |
el | Greek | lv | Latvian |
en | English | nb_NO | Norwegian |
en_AU | English (Australia) | ne | Nepalese |
en_AU_ocker | English (Australia Ocker) | nl | Dutch |
en_BORK | English (Bork) | nl_BE | Dutch (Belgium) |
en_CA | English (Canada) | pl | Polish |
en_GB | English (Great Britain) | pt_BR | Portuguese (Brazil) |
en_IE | English (Ireland) | pt_PT | Portuguese (Portugal) |
en_IND | English (India) | ro | Romanian |
en_NG | Nigeria (English) | ru | Russian |
en_US | English (United States) | sk | Slovakian |
en_ZA | English (South Africa) | sv | Swedish |
es | Spanish | tr | Turkish |
es_MX | Spanish (Mexico) | uk | Ukrainian |
fa | Farsi | vi | Vietnamese |
fi | Finnish | zh_CN | Chinese |
fr | French | zh_TW | Chinese (Taiwan) |
fr_CA | French (Canada) | zu_ZA | Zulu (South Africa) |
有些地区设置可能没有完整的数据集,比如说,有些语言可能缺少某些数据集,例如中文(zh_CN
)可能没有 lorem
数据集,但韩语(ko
)有。在这种情况下,Bogus
会默认使用英文(en
)的数据集。换句话说,如果找不到特定语言的数据集,就会退而使用英文的数据集。如果您有兴趣帮助贡献新的地区设置或更新现有的设置,请查看我们的创建地区设置页面获取更多信息。
来验证一下
[Theory]
[InlineData(null)]
[InlineData("zh_CN")]
public void Locales_ConfigTest(string? locale)
{
//default
var faker = locale is null ? new Faker<Staff>() : new Faker<Staff>(locale);
faker.RuleFor(u => u.Name, f => f.Person.FullName)
.RuleFor(u => u.Email, f => f.Person.Email)
.RuleFor(u => u.Age, f => f.Random.Number(18, 60))
.RuleFor(u => u.Addresses, f => f.MakeLazy(f.Random.Number(1, 3), () => f.Address.FullAddress()).ToList())
.RuleFor(u => u.Created, f => f.Date.PastOffset());
var staff = faker.Generate();
var consoleType = locale is null ? "default" : locale;
testOutputHelperAccessor.Output?.WriteLine($"{consoleType}:{JsonConvert.SerializeObject(staff)}");
}
OutPut
default:{"Id":0,"Name":"Clyde Price","Email":"Clyde17@yahoo.com","Age":39,"Addresses":["46277 Abraham Parkways, South Spencerland, Guadeloupe","6470 Porter Island, Lesliehaven, Chad","10804 Halvorson Brook, Ninaton, Iran"],"Created":"2023-04-30T11:31:35.5106219+08:00"}
zh_CN:{"Id":0,"Name":"昊焱 尹","Email":"_82@yahoo.com","Age":58,"Addresses":["孙桥5号, 珠林市, Costa Rica"],"Created":"2024-02-11T08:16:49.1807504+08:00"}
可以看出默认是en
英文,通过设置locale
可以实现国际化的输出。
生成相同数据集
// 如果您希望生成可重复的数据集,请设置随机数种子。
Randomizer.Seed = new Random(8675309);
这段代码用于设置随机数生成器的种子,以便生成可重复的数据集。通过指定一个固定的种子值,可以确保每次运行生成的随机数据都是相同的,从而实现数据集的重复性。
这个比较有意思,我们来做个 demo
,要求随机生成五个对象 要求下一次运行生成的还是同一批对象。
用Bogus
的 Seed
就很容易实现。
[Fact]
public void Bogus_Compare_SeedTest()
{
// Arrange
var faker = new Faker<Staff>()
.RuleFor(u => u.Name, f => f.Person.FullName)
.RuleFor(u => u.Email, f => f.Person.Email)
.RuleFor(u => u.Age, f => f.Random.Number(18, 60))
.RuleFor(u => u.Addresses, f => f.MakeLazy(f.Random.Number(1, 3), () => f.Address.FullAddress()).ToList())
.RuleFor(u => u.Created, f => f.Date.PastOffset());
// Act
var staffs1 = Enumerable.Range(1, 5)
.Select(_ => faker.UseSeed(_).Generate())
.ToList();
OutputStaffInformation(staffs1, "第一次");
var staffs2 = Enumerable.Range(1, 5)
.Select(_ => faker.UseSeed(_).Generate())
.ToList();
OutputStaffInformation(staffs2, "第二次");
// Assert
Assert.True(staffs1.All(staff1 => staffs2.Any(staff2 => staff1.Name == staff2.Name && staff1.Email == staff2.Email)));
}
private void OutputStaffInformation(List<Staff> staffs, string iteration)
{
foreach (Staff staff in staffs)
{
testOutputHelperAccessor.Output?.WriteLine($"{iteration}: name: {staff.Name}, email: {staff.Email}");
}
}
Arrange
部分初始化了一个Faker<Staff>
实例,并定义了一系列规则来生成Staff
对象。Act
部分通过使用不同的种子值,生成了两组包含 5 个Staff
对象的列表,并输出了每个Staff
对象的姓名和邮箱信息。Assert
部分使用断言验证了两组生成的Staff
列表中是否存在具有相同姓名和邮箱的对象,即通过 All 和 Any
方法进行比较。
通过使用不同的种子值来生成多组数据,然后断言这些数据中是否存在相同的姓名和邮箱信息。
Bogus Api 支持
Bogus
之所以提供这么方便的假数据生成,得益于封装了开箱即用的获取各类数据的方法,如:
Address
- ZipCode - 获取邮政编码。
- City - 获取城市名称。
- StreetAddress - 获取街道地址。
- CityPrefix - 获取城市前缀。
- CitySuffix - 获取城市后缀。
- StreetName - 获取街道名称。
- BuildingNumber - 获取建筑编号。
- StreetSuffix - 获取街道后缀。
- SecondaryAddress - 获取次要地址,如 '公寓 2' 或 '321 号套房'。
- County - 获取县名。
- Country - 获取国家。
- FullAddress - 获取完整地址,包括街道、城市、国家。
- CountryCode - 获取随机的 ISO 3166-1 国家代码。
- State - 获取随机州名。
- StateAbbr - 获取州名缩写。
- Latitude - 获取纬度。
- Longitude - 获取经度。
- Direction - 生成基数或序数方向,例如:西北、南、西南、东。
- CardinalDirection - 生成基数方向,例如:北、南、东、西。
- OrdinalDirection - 生成序数方向,例如:西北、东南、西南、东北。
Commerce
- Department - 获取随机商务部门。
- Price - 获取随机产品价格。
- Categories - 获取随机产品类别。
- ProductName - 获取随机产品名称。
- Color - 获取随机颜色。
- Product - 获取随机产品。
- ProductAdjective - 随机产品形容词。
- ProductMaterial - 随机产品材料。
- Ean8 - 获取随机的 EAN-8 条形码号码。
- Ean13 - 获取随机的 EAN-13 条形码号码。
后面的可以查看官网 Api
官网地址在文末...
Bogus 库提供了丰富的 API 支持,涵盖了各种数据类型和用例,包括地址、商务、日期、金融、图片、互联网、Lorem 文本、姓名、电话等方面的虚假数据生成方法。
EFCore 利用 Bogus 生成种子数据
在我们的Sample.Repository
中设置种子数据
- 使用
Bogus
库生成虚假数据,填充到 Staffs 列表
public class FakeData
{
public static List<Staff> Staffs = [];
public static void Init(int count)
{
var id = 1;
var faker = new Faker<Staff>()
.RuleFor(_ => _.Id, f => id++)
.RuleFor(u => u.Name, f => f.Person.FullName)
.RuleFor(u => u.Email, f => f.Person.Email)
.RuleFor(u => u.Age, f => f.Random.Number(18, 60))
.RuleFor(u => u.Addresses, f => f.MakeLazy(f.Random.Number(1, 3), () => f.Address.FullAddress()).ToList())
.RuleFor(u => u.Created, f => f.Date.PastOffset());
var staffs = faker.Generate(count);
FakeData.Staffs.AddRange(staffs);
}
}
Program
写入 1000 条种子数据
using (var context = app.Services.CreateScope().ServiceProvider.GetRequiredService<SampleDbContext>())
{
context.Database.EnsureCreated();
FakeData.Init(1000);
await context.Staffs.AddRangeAsync(FakeData.Staffs);
await context.SaveChangesAsync();
}
我这地方用的是Microsoft.EntityFrameworkCore.InMemory
内存数据库,正常如果使用像Sqlserver
,MySQL
等CodeFirst
模式可以在 DbContext 的OnModelCreating
配置种子数据。
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
//FakeData.Init(1000);
//builder.Entity<Staff>().HasData(FakeData.Staffs);
}
来测试一下
通过我们Sample.Api
提供的GetAll
的方法测试一下种子数据
正好一千条测试数据,大功告成。
最后
在软件开发中,使用 Bogus
可以极大地简化测试数据的创建过程,同时结合 EFCore
的种子数据功能,可以快速生成并初始化数据库中的虚假数据。这种方法不仅提高了开发效率,还能确保测试数据的质量和一致性。通过本文的示例和说明,希望您能更加熟悉如何利用 Bogus
和 EFCore
来生成模拟数据和种子数据,从而为软件开发过程提供更好的支持和帮助,我们有大量数据的测试需求时,也不用再为创造数据而烦恼。
欢迎关注笔者公众号一起学习交流,获取更多有用的知识~
Bogus 实战:使用 Bogus 和 EFCore 生成模拟数据和种子数据【完整教程】的更多相关文章
- php中CURL技术模拟登陆抓取数据实战,抓取某校教务处学生成绩。
这两天有基友要php中curl抓取教务处成绩的源码,用于微信公众平台的开发.下面笔者只好忍痛割爱了.php中CURL技术模拟登陆抓取数据实战,抓取沈阳工学院教务处学生成绩. 首先,教务处登录需要验证码 ...
- IdentityServer4使用EFCore生成MySql时的小bug
EFCore生成PersistedGrantDbContextModelSnapshot的时候发现 b.Property<string>("Data") .IsRequ ...
- 为django平台生成模拟用户,建立用户组,并将用户加入组
书接上篇BLOG. 当我们可以用manage.py自定义命令来生成模拟数据时, 我们面对的就是如何操作ORM的问题了. 这两天,我为我们的内部系统的所有数据表,都生成了模拟数据. 有几个心得,记录于此 ...
- mock的使用二(根据数据模板生成模拟数据)
Mock.mock( rurl?, rtype?, template|function( options ) ) 根据数据模板生成模拟数据. Mock.mock( template ) 根据数据模板生 ...
- 在java中调用mockjs生成模拟数据
一.手写版 在前端有个模拟数据的神器 Mock.js 能生成随机数据,拦截 Ajax 请求,然后我觉得他的这个生成随机数据不错.然后我就到度娘一顿操作,没找到类似的java实现,于是就有了下面的代码: ...
- java版gRPC实战之一:用proto生成代码
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- Flask实战-留言板-使用Faker生成虚拟数据
使用Faker生成虚拟数据 创建虚拟数据是编写Web程序时的常见需求.在简单的场景下,我们可以手动创建一些虚拟数据,但更方便的选择是使用第三方库实现.流行的python虚拟数据生成工具有Mimesis ...
- R语言实战(三)——模拟随机游走数据
一.模拟随机游走数据示例 x <- matrix(0,1000,1) for(i in 1:1000){ x[i+1] <- x[i]+rnorm(1) } plot(x,type=&qu ...
- Electron-vue实战(三)— 如何在Vuex中管理Mock数据
Electron-vue实战(三)— 如何在Vuex中管理Mock数据 作者:狐狸家的鱼 本文链接:Vuex管理Mock数据 GitHub:sueRimn 在vuex中管理mock数据 关于vuex的 ...
- php中CURL实现模拟登录并采集数据
在php中采集我们用的是简单的采集方式(例如file_get_contents)就无法做到了,但是如果想模拟登录用户并采集利用它就没办法了,我们可利用CURL函数来实现模拟登录并采集数据 这里要说一些 ...
随机推荐
- InputNumberZen.vue 数字输入 支持两位小数
<template> <span style="width: 200px; display: inline-block;"> <Input v-mod ...
- iview select 下拉 多选 数组 外面包一层 数组改逗分,外层不能用v-model 要用 :value @input,input里面要把对象解构下,才能过验证 - vue
iview select 下拉 多选 数组 外面包一层 数组改逗分,外层不能用v-model 要用 :value @input,input里面要把对象解构下,才能过验证 - vue
- 【大语言模型基础】-详解Transformer原理
一.Transformer Transformer最开始用于机器翻译任务,其架构是seq2seq的编码器解码器架构.其核心是自注意力机制: 每个输入都可以看到全局信息,从而缓解RNN的长期依赖问题. ...
- 基于stm32H730的解决方案开发之freertos系统解析
一 概述 在嵌入式小系统领域,freertos是一个非常厉害的角色.它和小芯片结合,能迸发出非常大的威力.这里在H730上使用了这个freertos,是应该做一个总结和备忘. 二 实例解析 1 线程初 ...
- C#事件(event)的理解
一.多播委托的应用--观察者模式 遇到一个开发的问题? 面试者:以面向对象的思想实现一下的场景: 猫:Miao一声,紧接着引发了一系列的行为~ Miao:引发了一系列的动作: 从代码层面来说:代码这样 ...
- zhelper-cvtool
https://github.com/bbqz007/zhelper-cvtool cvtool images/SuperMario.mp4 anno,cascade cvtool images/Su ...
- 记本地新建一个gradle方式springboot项目过程
打算使用gradle在idea新建个springboot项目,然后坑很多,记录一下 原来我的idea应该是社区版,新建项目时候没有可以选择spring相关配置,然后卸载了重装,之前问题是启动是启动起来 ...
- python script 编写摘要(一)
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明 本文作为本人csdn blog的主站的备份.(Bl ...
- API和String字符串介绍
API 1.如何使用Java已经写好的东西(方法,类) API(Application programming interface):应用程序编程接口 简单理解:API就是别人已经写好了的东西,我们不 ...
- 记录--服务端推送到Web前端有哪几种方式?
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 这个问题? 这个问题一般会出现在面试题里面,然后回答一些诸如轮询.WebSocket之类的答案.当然,实际开发中,也会遇到类似别人给你赞了 ...