Generowanie danych testowych
W jednym z wcześniejszych wpisów pokazałem, jak za pomocą dwóch bibliotek (Nbuilder oraz Faker.NET) wygenerować dane testowe. Przez dłuższy czas korzystałem z tamtego rozwiązania, natomiast ostatnio kolega z pracy pokazał mi inną, ciekawszą bibliotekę, która łączy działanie Nbuildera oraz Faker.NET. Do tego ma kilka dodatkowych funkcji, które się przydają. Biblioteka nazywa się Bogus (strona projektu) i jest to port biblioteki Faker.js.
Po pierwsze – biblioteka umożliwia wygenerowanie jednego lub wielu obiektów, gdzie dane, podobnie jak w Faker.NET, wyglądają na realne (nie są to generyczne wartości na podstawie nazwy właściwości, jak to się dzieje w Nbuilder).
Po drugie – autor biblioteki wzorował się na bibliotece FluentValidation, więc sposób pracy z Bogus jest bardzo podobny, co bardzo przypadło mi do gustu, jako że jestem wielkim fanem FluentValidation.
Bogus
Myślę, że najlepiej przejść od razu do dema i zobaczyć, jak wygląda biblioteka w praktyce. Cały przykład jest dostępny na githubie, więc możesz go ściągnąć i samemu sprawdzić, jak działa Bogus.
Na potrzeby przykładu przygotowałem trzy klasy, które wypełnimy danymi: User, Product oraz Order. Klasa reprezentująca zamówienia będzie korzystać z klasy użytkownika oraz produktu. Klasy wyglądają tak:
public class User | |
{ | |
public string FirstName { get; set; } | |
public string LastName { get; set; } | |
public string Email { get; set; } | |
public Gender Gender { get; set; } | |
} | |
public enum Gender | |
{ | |
Male = 0, | |
Female = 1 | |
} | |
public class Product | |
{ | |
public string Name { get; set; } | |
public string Company { get; set; } | |
public decimal Price { get; set; } | |
} | |
public class Order | |
{ | |
public DateTime Date { get; set; } | |
public User User { get; set; } | |
public Product Product { get; set; } | |
public int Count { get; set; } | |
public OrderStatus Status { get; set; } | |
} | |
public enum OrderStatus | |
{ | |
New, | |
Processing, | |
Completed, | |
Canceled | |
} |
Samo skorzystanie z biblioteki Bogus jest dość proste. Wystarczy utworzyć instancję generycznej klasy Faker, w której jako parametr przekazujemy typ, który będziemy wypełniać danymi. Następnie za pomocą metody RuleFor konfigurujemy sposób generowania danych. Samo utworzenie obiektu lub obiektów wykonuje się przez wywołanie metody Generate (gdy jako parametr przekażemy liczbę, wtedy dostaniemy kolekcję obiektów zamiast jednego obiektu). Poniżej przykład generowania listy 10 użytkowników:
var users = new Faker<User>(locale) | |
.StrictMode(true) | |
.RuleFor(u => u.Gender, (f, u) => f.PickRandom<Gender>()) | |
.RuleFor(u => u.FirstName, (f, u) => f.Name.FirstName((Bogus.DataSets.Name.Gender)u.Gender)) | |
.RuleFor(u => u.LastName, (f, u) => f.Name.FirstName((Bogus.DataSets.Name.Gender)u.Gender)) | |
.RuleFor(u => u.Email, (f, u) => f.Internet.Email(u.FirstName, u.LastName)) | |
.Generate(10); |
Jak widać powyżej, użycie biblioteki jest relatywnie proste. Co fajne, część danych może zależeć od innych danych. Na przykład imię oraz nazwisko mogą zależeć od płci użytkownika (tutaj jedyny minus dotyczący tego, że musimy przekazać wartość enuma Gender z biblioteki Bogus), czy w przypadku takich danych jak adres e-mail lub nazwa użytkownika możemy bazować na imieniu oraz nazwisku. Dzięki temu możemy uzyskać dane w stylu: Anne.Marsha78@yahoo.com dla imienia Anne oraz nazwiska Marsha. Takie działanie biblioteki powoduje, że dane są jeszcze bardziej realne.
Ciekawą opcją jest jeszcze ustawienie StrictMode na true (domyślnie ma wartość false, można to skonfigurować globalnie dla całej aplikacji). Ustawienie to wymusi określenie ról generowania danych dla wszystkich właściwości obiektu.
Poniżej znajduje się kod generujący dane dla pozostałych dwóch klas (Product oraz Order).
var products = new Faker<Product>(locale) | |
.StrictMode(true) | |
.RuleFor(p => p.Name, (f, p) => f.Commerce.ProductName()) | |
.RuleFor(p => p.Company, (f, p) => f.Company.CompanyName()) | |
.RuleFor(p => p.Price, (f, p) => f.Random.Decimal(0.01M, 1000M)) | |
.Generate(10); | |
var orders = new Faker<Order>(locale) | |
.StrictMode(true) | |
.RuleFor(o => o.User, (f, p) => f.PickRandom(users)) | |
.RuleFor(o => o.Product, (f, p) => f.PickRandom(products)) | |
.RuleFor(o => o.Count, (f, p) => f.Random.Int(1, 20)) | |
.RuleFor(o => o.Date, (f, p) => f.Date.Past(2)) | |
.RuleFor(o => o.Status, (f, p) => f.PickRandom<OrderStatus>()) | |
.Generate(100); |
Jak widać między innymi w powyższym kodzie, liczba typów danych dostępnych w bibliotece Bogus jest bardzo duża. Co ciekawe, możemy też używać wcześniej wygenerowanych danych – tak jak w przypadku obiektu użytkownika oraz produktu podczas generowania zamówień. Można w prosty sposób wybrać losowy obiekt z już istniejącej kolekcji.
Kolejną fajną funkcjonalnością biblioteki jest wsparcie dla lokalizowania wygenerowanych danych. Wspieranych jest kilkadziesiąt języków, w tym i język polski. Niestety w przypadku naszego języka część wygenerowanych danych jest w języku polskim, a część w angielskim, co wygląda dość dziwnie:
Sama zmiana języka generowania danych jest dość prosta. Najłatwiej stworzyć zmienną z kodem języka i później przekazywać ją w konstruktorze do klasy Faker:
string locale = "pl"; | |
var users = new Faker<User>(locale) | |
.Generate(10); |
Podsumowanie
Mam nadzieję, że biblioteka Bogus spodobała Ci się i będziesz jej używał w swoich projektach. Tak jak zaznaczam to w innych wpisach, cały kod znajduje się na githubie.
A może Ty znasz jakąś inną bibliotekę tego typu? Jeśli tak, to daj mi znać, z chęcią ją poznam. 🙂
Też bardzo lubię Bogusa, używam go stosunkowo często 🙂 Kiedyś nawet porównałem go do Autofixture i NBuildera https://dominikroszkowski.pl/2017/07/bogus-in-testing/
Zachęcam do rzucenia okiem na inny wariant składni, gdzie zamiast RuleFor używa się Rules – wydaje się dla mnie bardziej czytelny w przypadku większej ilości atrybutów.
Wcześniej również wykorzystywałem Nbuilder (tylko, że w połączeniu do Faker.NET – https://plawgo.pl/2018/03/02/nbuilder-oraz-faker-net-generowanie-danych/) ale Bogus generuje dużo fajniejsze dane (w szczególności możliwość bazowania na innych właściwościach). Więc Nbuilder został tylko do generowania danych w testach jednostkowych, bo tam Bogus się nie sprawdza.
Co do składni Rules – sprawdzałem i jakoś bardziej lubie z RuleFor, ale to prawdopodobnie wynika głównie z tego, że od lat używam Fluent Validation, więc ta składnia jest dla mnie jakoś bardziej normalna 🙂