Bogus 實戰:使用 Bogus 和 EFCore 生成模擬資料和種子資料【完整教程】

董瑞鹏發表於2024-04-17

引言

上一章我們介紹了在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.jsC#移植版本,並受到 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來配置實體的屬性

看一下生成的測試資料

image

Run Tests

image

單元測試成功,有了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,要求隨機生成五個物件 要求下一次執行生成的還是同一批物件。
BogusSeed 就很容易實現。

   [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 方法進行比較。

透過使用不同的種子值來生成多組資料,然後斷言這些資料中是否存在相同的姓名和郵箱資訊。

image

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 文字、姓名、電話等方面的虛假資料生成方法。

image

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,MySQLCodeFirst模式可以在 DbContext 的OnModelCreating配置種子資料。

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

來測試一下

透過我們Sample.Api提供的GetAll的方法測試一下種子資料

image

正好一千條測試資料,大功告成。

最後

在軟體開發中,使用 Bogus 可以極大地簡化測試資料的建立過程,同時結合 EFCore 的種子資料功能,可以快速生成並初始化資料庫中的虛假資料。這種方法不僅提高了開發效率,還能確保測試資料的質量和一致性。透過本文的示例和說明,希望您能更加熟悉如何利用 BogusEFCore 來生成模擬資料和種子資料,從而為軟體開發過程提供更好的支援和幫助,我們有大量資料的測試需求時,也不用再為創造資料而煩惱。

😄歡迎關注筆者公眾號一起學習交流,獲取更多有用的知識~
image

  • Bogus Github

  • 本文完整原始碼

相關文章