Jak usunąć dane z bazy używając Respawn
Do resetowania stanu bazy w automatycznych testach można użyć Sql Server Snapshots. Innym podejściem, które możemy wykorzystać podczas pracy, jest po prostu usuwanie z bazy danych, które zostały dodane podczas wykonywania testów. Tytułowa biblioteka Respawn umożliwia realizację czegoś takiego w bardzo prosty sposób, ale ma także swoje wady. Zobacz, co umożliwia i gdzie może Ci się ona przydać.
Daniel Plawgo. .NET Developer w SoftwareHut, Trainer, Consultant, Bloger (plawgo.pl). Od wielu lat jest związany ze społecznością Microsoft. Początkowo był Student Partnerem i prowadził grupę .NET Eastgroup na Wydziałe Matematyki i Informatyki UWM. Później był Student Consultantem odpowiedzialnym za studenckie grupy .NET z północnej Polski. Od prawie 10 lat prowadzę zawodową grupę .NET OLMUG u nas w Olsztynie.
Respawn jest prostą biblioteką, która ułatwia usuwanie danych. Jak podaje autor biblioteki, jest to inteligentne narzędzie do czyszczenia bazy danych do testów integracyjnych. „Inteligentne” oznacza, że biblioteka w pierwszej kolejności analizuje schemat bazy danych, aby na jej podstawie zdecydować o kolejności usuwania danych z bazy. Dzięki temu nie musimy na przykład zdejmować ograniczeń dla kluczy obcych w bazie, ponieważ biblioteka w pierwszej kolejności usunie rekordy zależne, a później rekord główny.
Już tutaj widać potencjalnie największy problem biblioteki, który może zadecydować, czy postanowisz jej użyć, czy nie. Jest nim to, że biblioteka usuwa wszystkie dane z tabel, które nas interesują. Jest to duża różnica w stosunku do migawek ze Sql Servera – tam wracaliśmy do określonego stanu bazy, gdzie w tabelach mogły znajdować się wcześniej przygotowane dane. W przypadku Respawn reset bazy oznacza po prostu usunięcie danych z tabel.
Spis treści
Respawn w akcji
Na potrzeby dzisiejszego wpisu przygotowałem prostą aplikację konsolową, w której znajduje się prosta struktura bazy z relacją jeden do wielu oraz wiele do wielu. Użyłem Entity Framework z podejściem Code First, a w aplikacji znajdują się następujące klasy modelu:
public class BaseModel { public BaseModel() { IsActive = true; } public int Id { get; set; } public bool IsActive { get; set; } public DateTime CreatedDate { get; set; } public string CreatedUser { get; set; } public DateTime UpdatedDate { get; set; } public string UpdatedUser { get; set; } } public class Category : BaseModel { public string Name { get; set; } } public class Book : BaseModel { public string Title { get; set; } public int CategoryId { get; set; } public virtual Category Category { get; set; } public virtual ICollection<Person> Authors { get; set; } } public class Person : BaseModel { public string FirstName { get; set; } public string LastName { get; set; } public virtual ICollection<Book> Books { get; set; } }
Klasa BaseModel zawiera podstawowe właściwości, które chce mieć w każdej klasie modelu. Klasa Category jest powiązana relacją jeden do wielu z klasą Book, a następnie klasa Book jest powiązana relacją wiele do wielu z klasą Person.
W przykładzie dodałem jeszcze prostą metodę SeedData, która wrzuca do bazy dane wygenerowane przez bibliotekę NBuilder. W późniejszym kroku dane te będą usuwane przez Respawn. Tutaj we wpisie nie będę już wklejał kodu tej metody, bo nie jest on istotny w kontekście omawiania biblioteki Respawn.
Samo użycie biblioteki Respawn jest dość proste i wygląda tak:
class Program { private static Checkpoint _checkpoint = new Checkpoint { TablesToIgnore = new[] { "__MigrationHistory" } }; static void Main(string[] args) { SeedData(); _checkpoint.Reset(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString).Wait(); } }
Na początku definiujemy instancję klasy Checkpoint, w której określamy konfigurację. W kodzie widać najczęściej używaną właściwość TablesToIgnore, w której określamy tabele – z nich biblioteka nie usunie danych podczas resetu bazy. W moim przykładzie nie chcę, aby biblioteka usuwała dane z tabeli z migracjami Entity Framework.
Samo usunięcie danych odbywa się poprzez wywołanie metody Reset na obiekcie klasy Checkpoint i przekazaniu connection stringa do bazy w parametrze metody. Warto zauważyć, że metoda jest asynchroniczna (zwraca obiekt klasy Task), więc w tym przypadku dodałem jeszcze wywołanie metody Wait.
W efekcie wykonywania powyższego kodu Respawn wygenerowało i wykonało takiego sqla usuwającego dane z bazy:
DELETE "dbo"."PersonBooks"; DELETE "dbo"."People"; DELETE "dbo"."Books"; DELETE "dbo"."Categories";
Widać, że kolejność usuwania danych jest poprawna. W tym sensie, że najpierw usuwane są dane z tabeli PersonBooks, która jest elementem relacji wiele do wielu, a na samym końcu są usuwane dane z tabeli Categories, która nie zależy od innych tabel.
Natomiast pobranie metadanych o bazie tuż przed usunięciem danych wykonało takiego sqla:
select fk_schema.name, so_fk.name, pk_schema.name, so_pk.name, sfk.name from sys.foreign_keys sfk inner join sys.objects so_pk on sfk.referenced_object_id = so_pk.object_id inner join sys.schemas pk_schema on so_pk.schema_id = pk_schema.schema_id inner join sys.objects so_fk on sfk.parent_object_id = so_fk.object_id inner join sys.schemas fk_schema on so_fk.schema_id = fk_schema.schema_id where 1=1 AND so_pk.name NOT IN (N'__MigrationHistory')
Konfiguracja Respawn
Poza pokazanym wcześniej użyciem właściwości TablesToIgnore możemy ustawić w klasie Checkpoint jeszcze kilka innych właściwości:
- TablesToInclude – ręczne określenie tabel, z których ma nastąpić usunięcie danych,
- SchemasToInclude oraz SchemasToExclude – określamy usunięcie danych lub nie na podstawie schematu w bazie,
- WithReseed – resetuje autonumerowanie dla kluczy w tabelach, dzięki czemu dodanie nowych rekordów do bazy po resecie spowoduje, że klucze będą ponownie generowane od wartości startowej (na ogół 1).
Myślę, że na ogół najsensowniejszą opcją jest skorzystanie z dwóch właściwości. W TablesToIgnore określamy tabele, z których nie chcemy usuwać danych (np. tabele z migracjami Entity Framework). Drugą właściwością jest WithReseed ustawione na true, aby dodawane rekordy do bazy miały zawsze ten sam klucz.
Przykład
Na githubie już tradycyjnie znajdziesz przykład, którego użyłem do pracy nad tym wpisem. Po jego pobraniu należy w app.config ustawić connection stringa do testowej bazy danych.
Podsumowanie
Respawn jest ciekawą alternatywą dla Sql Server Snapshots w kwestii resetowania bazy do znanego stanu. Jak podaje autor, użycie Respawn jest dużo bardziej efektywne (testy wykonują się szybciej) niż użycie migawek. W sytuacji, gdy czyszczenie danych nie jest problemem w Twoich scenariuszach testowych, myślę, że Respawn jest ciekawą alternatywą.
Osobiście zostaję przy użyciu migawek. W swoich testach WebApi bardzo często zakładam, że w bazie na starcie znajdują się określone dane i użycie Respawna bardzo by mi utrudniło pisanie testów. Jednak próbuję pozmieniać kod samej biblioteki Respawn zamieszczony na githubie, aby bardziej selektywnie usuwać dane. Jak coś ciekawego mi z tego wyjdzie, to z chęcią podzielę się tym.
Artykuł został pierwotnie opublikowany na plawgo.pl. Zdjęcie główne artykułu pochodzi z unsplash.com.