Podczas pisania aplikacji w MVC, w której wykorzystujemy TDD często natrafiamy na problem testowania warstwy bazodanowej. Najczęściej są to dwa podstawowe problemy: szybkość oraz odwracanie zmian które zaszły podczas testowania dodawania/usuwania itp. Problemy te często przekładają się na zniechęcenie do tworzenia testów, jak i sporym utrudnieniu tworzenia testowalnych aplikacji.
Potrzebne jest więc rozwiązanie, które pozwoli na łatwe testowanie kodu na obiektach w pamięci, jak i łatwą integrację w późniejszym stadium z bazą danych. Wszystkie te problemy może rozwiązać implementacja wzorca Repository Pattern.
Realny problem:
W przykładzie uczestniczą dwie klasy: Person oraz Car. Klasa Person reprezentuje jakiegoś człowieka, klasa Car - samochód.
Założenia: aplikacja ma pracować z bazą danych i mieć możliwość wykonywania testów bez potrzeby łączenia się do niej.
Wykorzystamy więc tutaj wzorzec repozytorium. Trochę teorii na początek. Wzorzec repozytorium zakłada utworzenie osobnego repozytorium dla każdej z klas. Repozytorium porozumiewa się z naszymi zmapowanymi obiektami i wykonuje żądania użytkownika.
W naszym przykładzie możemy łatwo wyodrębnić szereg metod które będą wykorzystywane np. na obiekcie klasy Person:
AddPerson, DeletePersonById, UpdatePerson, GetAllPersons, GetPersonById, GetPersonCount itd.
Druga z klas - Car będzie mieć podobne metody.
Istnieje więc szereg podobnych metod. Możemy je wyodrębnić jako wspólny interfejs IRepository
Następnie stworzymy konkretne klasy implementujące nasze interfejsy: FakePersonRepository, SqlPersonRepository, FakeCarRepository, SqlCarRepository. Zapewni nam to łatwość przy testowaniu przy użyciu frameworków NUnit oraz Mocq.
Tak więc po kolei. Kod dwóch wcześniej omówionych klas:
Teraz tworzymy interfejs IRepository:
Kod:
public interface IRepository<T>
{
List<T> GetAll();
T GetById(int id);
int Count();
void Add(T entity);
void Update(T entity);
void DeleteById(int id);
void DeleteAll();
}
public interface IPersonRepository : IRepository<Person>
{
void AddCarToPerson(Person person);
}
public interface ICarRepository : IRepository<Car>
{
}
Tworzymy następnie klasy implementujące ICarRepository oraz IPersonRepository w wersji działającej w pamięci:
public class FakeCarRepository : ICarRepository
{
private List<Car> _cars = new List<Car>();
public List<Car> GetAll()
{
return _cars;
}
public Car GetById(int id)
{
return _cars.Single(x => x.IdCar == id);
}
public int Count()
{
return _cars.Count;
}
public void Add(Car entity)
{
_cars.Add(entity);
}
public void Update(Car entity)
{
Car c = _cars.Single(x => x.IdCar == entity.IdCar);
c.Mark = entity.Mark;
c.ProductionYear = entity.ProductionYear;
}
public void DeleteById(int id)
{
_cars.Remove(_cars.Single(x => x.IdCar == id));
}
public void DeleteAll()
{
_cars.Clear();
}
}
class FakePersonRepository : IPersonRepository
{
private List<Person> _personList = new List<Person>();
public List<Person> GetAll()
{
return _personList;
}
public Person GetById(int id)
{
try
{
return _personList.Where(x => x.IdPerson == id).SingleOrDefault();
}
catch (ArgumentNullException)
{
return null;
}
}
public int Count()
{
return _personList.Count;
}
public void Add(Person entity)
{
_personList.Add(entity);
}
public void Update(Person entity)
{
Person p = _personList.Where(x => x.IdPerson == entity.IdPerson).SingleOrDefault();
p = entity;
}
public void DeleteById(int id)
{
_personList.Remove(_personList.Where(x => x.IdPerson == id).SingleOrDefault());
}
public void DeleteAll()
{
_personList.Clear();
}
public void AddCarToPerson(Person person, Car car)
{
if (person.Cars == null)
{
person.Cars = new List<Car>();
}
person.Cars.Add(car);
}
}
W podobny sposób tworzymy klasy SqlPersonRepository. W tym przypadku jednak tworzymy klasę, która działa na bazie danych a nie w pamięci. Implementacja rozwiązania bazodanowego zależy przede wszystkim od wybranej technologi czy też ORM mappera.
Nasze rozwiązanie jest bardzo proste w dalszej pielęgnacji. Przy testowaniu korzystamy z naszych Faków, dla klienta dostarczamy wersję działającą bezpośrednio na bazie danych.
Brak komentarzy:
Prześlij komentarz