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

引言

上一章我们介绍了在

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

    列表中是否存在具有相同姓名和邮箱的对象,即通过 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 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 Github

  • 本文完整源代码

未经允许不得转载:大白鲨游戏网 » Bogus 实战:使用 Bogus 和 EFCore 生成模拟数据和种子数据【完整教程】