Marcin Biegała

5 minute read

Dziś wreszcie pojawi się trochę kodu :)
Mam nadzieję, że o zyskach przy okazji pisania testów jednostkowych, czy stosowaniu TDD nie muszę pisać. Jednak te z reguły możemy docenić dopiero w dłuższej perspektywie pracy z projektem. Tu i teraz, w chwili pisania kodu, nie oszukujmy się – jest to dodatkowa praca.
Ale nawet jeśli staramy się trzymać dobrych praktyk, zasad SOLID, a nasze metody są krótkie i przemyślane, zdarzają się projekty gdzie operujemy na rozbudowanych modelach danych, z dużą ilością pól i właściwości.

To odbija się czkawką podczas pisania testów w dwojaki sposób:
– aby przetestować nasze metody, potrzebujemy model wypełniony danymi, już możemy sobie wyobrazić te kilka, kilkanaście linii kodu tylko po to, by odtworzyć pewien stan obiektu i przekazać go do naszej metody w teście
– potrzebujemy porównać czy nasze modele są sobie równe, w rozumieniu że wszystkie ich właściwości są sobie równe.
Oba problemy wydają się trywialne, ale w pewien sposób ‘zatruwają’ nasz kod. Albo nasze testy rozrastają się o kilkanaście linijek nie związanych z ‘intencją’ naszego testu, albo zaczynamy tworzyć całą strukturę, a wręcz framework do naszych testów jednostkowych, żeby w jakiś sposób wyekstrahować ten powtarzalny kod poza pliki testów.
Całość może się odbić negatywnie na naszym zaangażowaniu w testy, bo po prostu nie będzie nam się chciało pisać tego całego dodatkowego kodu, bo przecież zaraz kończę pracę, a sam kod aplikacji już napisałem.

Jak możemy sobie pomóc ? Otóż stosując dwie proste biblioteki: NBuilder i Compare .NET Objects

NBuilder jak można się domyśleć, wspomaga nas w budowaniu obiektów.

Wyobraźmy sobie na wstępie, że w naszym kodzie mamy następującą klasę:

public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string PhoneNumber { get; set; }
        public string AlternatePhoneNumber { get; set; }
        public string AddressStreet { get; set; }
        public string AddressCity { get; set; }
        public string AddressPostalCode { get; set; }
        public string AddressCountry { get; set; }
        public DateTime BirthDate { get; set; }
        public string TwitterHandle { get; set; }
        public string WebSiteAddress { get; set; }
        public DateTime RegistrationDate { get; set; }
        public Guid RegistrationToken { get; set; }
    }

Niby nic nadzwyczajnego, ale trochę tych właściwości ktoś wymyślił. My, zabieramy się za przetestowanie jakiegoś fragmentu kodu, który z tej klasy korzysta – np. podczas serializacji.
Przy jednym teście moglibyśmy się pokusić o przygotowanie takiego obiektu samemu. Przy drugim, już zaczniemy kombinować nad jakimiś metodami do generowania losowych tekstów itp., a przy piątym zbudujemy całą fabrykę takich testowych obiektów ;) Bo przecież można.

Można, ale nie trzeba, bo ktoś już za nas ten problem rozwiązał.
Zaczynamy od dodania do projektu nuget NBuilder i już możemy zbudować taki oto kod:

var allRandom = Builder<Person>.CreateNew().Build();

            var listOfRandomPeople = Builder<Person>.CreateListOfSize(20).Build();

            var personWithSpecificId = Builder<Person>.CreateNew()
                                                        .With(x => x.Id = 666)
                                                        .With(x => x.FirstName = "Jaś")
                                                        .With(x => x.LastName = "Fasola")
                                                        .Build();

            var birthDate = DateTime.Now;
            var listWhereFirstTwoHasSameBirthDate = Builder<Person>.CreateListOfSize(26)
                                                    .TheFirst(2)
                                                        .With(x => x.BirthDate = birthDate)
                                                    .Build();

            var listWhereEveryoneHasSameName = Builder<Person>.CreateListOfSize(26)
                                                    .All()
                                                        .With(x => x.FirstName = "John")
                                                        .With(x => x.LastName = "Doe")
                                                    .Build();

Ciekawi co też pojawiło się jako wartości w naszych obiektach ? Proszę bardzo:

NBuilder i Compare .Net Objects - Zawartość obiektu z NBuilder

Oczywiście to tylko fragment możliwości NBuilder. Możemy m.in wywoływać konkretne konstruktory, przekazywać do nich zadane parametry, wywoływać metody na wygenerowanych obiektach, a wszystko to ładnie schowane za ‘fluent api’.
I to zrobione za nas!

No to skoro już mamy gotowe obiekty do przetestowania naszego serializowania, przydałoby się sprawdzić, czy to co odczytaliśmy, jest dokładnie tym co chcieliśmy zapisać. I znów, moglibyśmy się pokusić o kawałek autorskiego kodu, który nam ten problem rozwiąże, ale wtedy nie przybliżyłbym Wam naszego drugiego bohatera, czyli biblioteki Compare .NET Objects.
Dość gadania, niech kod sam przemówi:

var people = Builder<Person>.CreateListOfSize(2)
                                        .TheFirst(1).With(x => x.FirstName = "Jo")
                                        .TheNext(1).With(x => x.FirstName = "Mary")
                                        .Build();

            var personJo = people[0];
            var personMary = people[1];

            var compareLogic = new CompareLogic();
            compareLogic.Config.MaxDifferences = Int32.MaxValue;

            var comparisonResultA = compareLogic.Compare(personJo, personMary);
            var comparisonResultB = compareLogic.Compare(personJo, personJo);

            Console.WriteLine("Result A [personJo vs personMary]: " + comparisonResultA.AreEqual);
            Console.WriteLine("Differences A [personJo vs personMary]: " + comparisonResultA.DifferencesString);

            Console.WriteLine();

            Console.WriteLine("Result B [personJo vs personJo]: " + comparisonResultB.AreEqual);
            Console.WriteLine("Differences B [personJo vs personJo]: " + comparisonResultB.DifferencesString);

NBuilder i Compare .Net Objects - Wynik działania Compare .Net Objects Po uruchomieniu, w konsoli otrzymamy zarówno stwierdzenie, czy oba obiekty są równe (true/false) jak również listę różnic (jakie pole i jakie wartości).
Co prawda wypisane wprost do konsoli nie wygląda zbyt klarownie, ale do dyspozycji mamy również odpowiednie obiekty, po których możemy sobie iterować w kodzie i zrobić z wartościami co nam się żywnie podoba.

Dodatkowo biblioteka posiada dość dużo opcji pozwalających nam wpłynąć na logikę porównywania. Możemy m.in określić ile różnic nas interesuje (w przykładzie użyłem Int32.MaxValue, żeby pokazać wszystkie różnice, ale równie dobrze możemy zatrzymać się na pierwszej), czy chcemy porównywać prywatne i statyczne pola, możemy też podpiąć własny komparator, lub po prostu zignorować część właściwości.

Co prawda przedstawione biblioteki nie wystrzelą rakiety w kosmos, nie wyleczą nikogo z raka, ani nie zredukują bezrobocia, ale są to jedne z tych narzędzi, które przewijają się w większości projektów, w których biorę udział. Bo kto kiedyś nie próbował np. zautomatyzowania porównywania obiektów samemu ? Walki z refleksją, nerw i wszechobecnych hardcodowanych stringów.
Tu macie to na wyciągnięcie nugeta ;)

 

comments powered by Disqus