當前位置: 妍妍網 > 碼農

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