当前位置: 欣欣网 > 码农

Bogus 实战:使用 Bogus 和 EFCore 生成模拟数据和种子数据【完整教程】

2024-04-18码农

引言

上一章我们介绍了在 xUnit 单元测试中用 xUnit.DependencyInject 来使用依赖注入,上一章我们的 Sample.Repository 仓储层有一个批量注入的接口没有做单元测试,今天用这个示例来演示一下如何用 Bogus 创建模拟数据 ,和 EFCore 的种子数据生成

Bogus 的优势

  1. 丰富的数据生成支持 :Bogus 提供了广泛的 API 支持,涵盖了各种数据类型和用例,使得生成虚假数据变得非常灵活和方便。

  2. 重复性和可控性 :通过设置种子值,可以确保生成的虚假数据是可重复的,这对于需要一致的测试数据或示例数据非常有用。

  3. 易于使用 :Bogus 使用流畅的语法和简单的方法调用,使得生成虚假数据变得简单直观,即使是对库不熟悉的用户也可以快速上手。

  4. 内置规则和语义 :内置了许多常见数据类别的规则和语义,例如公司名称、产品名称、地址等,可以快速生成符合实际场景的数据。

  5. 灵活性 :除了内置规则外,还可以通过自定义规则来生成特定的数据,满足不同场景下的需求。

  6. 社区支持 :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 classStaff
{
publicint Id { getset; }
publicstring Name { getset; }
publicstring Email { getset; }
publicint? Age { getset; }
public List<string>? Addresses { getset; }
public DateTimeOffset? Created { getset; }
}

接下来对我们批量新增的接口进行单元测试,测试数据通过 Bogus 生成,先看使用在讲解用法。

生成 500 条测试数据保存到 DB

[Fact]
publicasync 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(1860))
.RuleFor(u => u.Addresses, f => f.MakeLazy(f.Random.Number(13), () => 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")]
publicvoidLocales_ConfigTest(string? locale)
{
//default
var faker = locale isnull ? 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(1860))
.RuleFor(u => u.Addresses, f => f.MakeLazy(f.Random.Number(13), () => f.Address.FullAddress()).ToList())
.RuleFor(u => u.Created, f => f.Date.PastOffset());
var staff = faker.Generate();
var consoleType = locale isnull ? "default" : locale;
testOutputHelperAccessor.Output?.WriteLine($"{consoleType}:{JsonConvert.SerializeObject(staff)}");
}

OutPut

default:{"Id":0,"Name":"Clyde Price","Email":"[email protected]","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":"[email protected]","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]
publicvoidBogus_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(1860))
.RuleFor(u => u.Addresses, f => f.MakeLazy(f.Random.Number(13), () => f.Address.FullAddress()).ToList())
.RuleFor(u => u.Created, f => f.Date.PastOffset());
// Act
var staffs1 = Enumerable.Range(15)
.Select(_ => faker.UseSeed(_).Generate())
.ToList();
OutputStaffInformation(staffs1, "第一次");
var staffs2 = Enumerable.Range(15)
.Select(_ => faker.UseSeed(_).Generate())
.ToList();
OutputStaffInformation(staffs2, "第二次");
// Assert
Assert.True(staffs1.All(staff1 => staffs2.Any(staff2 => staff1.Name == staff2.Name && staff1.Email == staff2.Email)));
}
privatevoidOutputStaffInformation(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 列表中是否存在具有相同姓名和邮箱的对象,即通过 A ll 和 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 classFakeData
    {
    publicstatic List<Staff> Staffs = [];
    publicstaticvoidInit(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(1860))
    .RuleFor(u => u.Addresses, f => f.MakeLazy(f.Random.Number(13), () => 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 配置种子数据。

    protectedoverridevoidOnModelCreating(ModelBuilder builder)
    {
    base.OnModelCreating(builder);
    //FakeData.Init(1000);
    //builder.Entity<Staff>().HasData(FakeData.Staffs);
    }

    来测试一下

    通过我们 Sample.Api 提供的 GetAll 的方法测试一下种子数据

    正好一千条测试数据,大功告成。

    最后

    在软件开发中,使用 Bogus 可以极大地简化测试数据的创建过程,同时结合 EFCore 的种子数据功能,可以快速生成并初始化数据库中的虚假数据。这种方法不仅提高了开发效率,还能确保测试数据的质量和一致性。通过本文的示例和说明,希望您能更加熟悉如何利用 Bogus EFCore 来生成模拟数据和种子数据,从而为软件开发过程提供更好的支持和帮助,我们有大量数据的测试需求时,也不用再为创造数据而烦恼。

  • Bogus Github [1]

  • 本文完整源代码 [2]

  • 参考资料

    [1]

    Bogus Github: https://github.com/bchavez/Bogus

    [2]

    本文完整源代码: https://github.com/Dong-Ruipeng/dotNetParadise-xUnit