DI w bibliotece .dll

Odpowiedz Nowy wątek
2019-08-30 19:14
0

Hej, piszę dla siebie małą bibliotekę w .Net.

W zasadzie to nigdy tego wcześniej nie robiłem. Chcę mieć po prostu kawałek kodu który będę sobie wykorzystywał co jakiś czas w różnych projektach.

Biblioteka ta mi trochę już urosła. Korzysta z różnych domen, i siłą rzeczy chciałem je porozdzielać po różnych klasach i komunikować się między nimi przez interfejsy, co by mieć je ogarnięte oraz móc sobie też przetestować lub mockować łatwiej.

I jakoś niespecjalnie wydaje się to praktyczne, bo później jest zabawa z IoC projektu który z biblioteki korzysta. Zastanawiam się czy:

  • biblioteki się testuje?
  • korzysta się w nich z IoC oraz DI? tzn wewnątrz dll-a.
  • czy zamyka się kilka domen w jednej bibliotece? np. operacje na stringu + IO + WebBrowser.

Pozdrawiam,
b.

edytowany 1x, ostatnio: bakunet, 2019-08-31 02:40

Pozostało 580 znaków

2019-09-04 10:21
0

Nie wiem jak to jest w C# ale w Javie widziałem kiedyś jakąś libkę która używała kontenera IoC i po prostu nie dało się jej użyć, bo gryzła się z aplikacją. Co jak ktoś w aplikacji ma już inne IoC albo to samo, ale w innej wersji? :)


Masz problem? Pisz na forum, nie do mnie. Nie masz problemów? Kup komputer...

Pozostało 580 znaków

2019-09-04 10:23
0
Shalom napisał(a):

Nie wiem jak to jest w C# ale w Javie widziałem kiedyś jakąś libkę która używała kontenera IoC i po prostu nie dało się jej użyć, bo gryzła się z aplikacją. Co jak ktoś w aplikacji ma już inne IoC albo to samo, ale w innej wersji? :)

Dobre pytanie, faktycznie to może być problem.

Pozostało 580 znaków

2019-09-04 12:15
1

Ręcznie. Czy to poprzez fabryki, czy w jakiejś głównej metodzie IX something = new X(); IY somethingelse = new Y(something); .... i tak sobie lecisz. Oczywiście nie jest to super wygodne, ale ma zaletę. Od razu Cię boli jak klasa ma multum zależności.
Jeśli to co masz to biblioteka, to użyj buildera, i zamknij to w nim, żeby użytkownik mógł łatwo sobie zbudować to co potrzebuje. Albo Fabrykę, w zależności co jest odpowiednie w Twoim przypadku.


Pozostało 580 znaków

2019-09-04 12:29
0
AreQrm napisał(a):

Ręcznie. Czy to poprzez fabryki, czy w jakiejś głównej metodzie IX something = new X(); IY somethingelse = new Y(something); .... i tak sobie lecisz. Oczywiście nie jest to super wygodne, ale ma zaletę. Od razu Cię boli jak klasa ma multum zależności.
Jeśli to co masz to biblioteka, to użyj buildera, i zamknij to w nim, żeby użytkownik mógł łatwo sobie zbudować to co potrzebuje. Albo Fabrykę, w zależności co jest odpowiednie w Twoim przypadku.

Sorry, ale zdążyłem usunąć pytanie, bo miałem olśnienie ;p

Ale udało się. Najpierw tworzę fasadę która przyjmuje w konstruktor interfejsy:

public class Facade
    {
        public Facade(IFilesService fileServ, IControlsService contrServ)
        {
            FilesService = fileServ;
            ControlsService = contrServ;
        }

        public virtual IFilesService FilesService { get; set; }
        public virtual IControlsService ControlsService { get; set; }
    }

W SUT w konstruktor fasady wrzucam nowe obiekty:

public class MainService : IMainService
    {
        private IFilesService _fileService;
        private IControlsService _controlsService;

        public UiControlsModel Controls { get; set; }
        public Facade Facade { get; set; }

        public MainService()
        {
            Initialize();
        }

        public void Initialize()
        {
            Facade = new Facade(new FilesService(), new ControlsService());
        }
...
}

A w konstruktorze testu obiekty podmieniam makietami.

public class MainServiceTests
    {
        private readonly Mock<IControlsService> _controlsServiceMock;
        private readonly Mock<IFilesService> _filesServiceMock;
        private readonly MainService _vm;

        private readonly UiControlsModel _controlsModelOff = new UiControlsModel() { Dwa = true, Jeden = true, Trzy = true };

        public MainServiceTests()
        {
            _controlsServiceMock = new Mock<IControlsService>();
            _filesServiceMock = new Mock<IFilesService>();

            _controlsServiceMock.Setup(c => c.SwitchOff())
                .Returns(_controlsModelOff);

            _vm = new MainService();
            _vm.Facade = new Facade(_filesServiceMock.Object, _controlsServiceMock.Object);
        }

        [Fact]
        public void SwitchOff_Called_ReturnsModel()
        {
            _vm.Controls = new UiControlsModel() { Jeden = false, Dwa = false, Trzy = false };

            _vm.SwitchOff();

            Assert.True(_vm.Controls.Trzy); //test przechodzi pozytywnie
        }
    }

Trochę mnie kłuje w oczy fakt, że w SUT muszę korzystać z własności publicznej Facade której przypisuję instancję, ale chyba inaczej się nie da... ? Czy publiczna public Facade Facade { get; set; } i nadpisanie w teście _vm.Facade to zła praktyka?

edytowany 3x, ostatnio: bakunet, 2019-09-04 12:46

Pozostało 580 znaków

2019-09-04 12:55
0

Zrób internal konstruktor i zrób publiczną fabrykę która będzie konstruować ten obiekt. Potem w testach możesz użyć InternalsVisibleTo i użyć internalowego konstruktora.

Pozostało 580 znaków

2019-09-04 12:57
4

Żeby dało się tego sensownie używać, to powinieneś mieć po prostu klasę, która wczytuje konfigurację z pliku, i zwraca typ IParamsLogger. Coś w rodzaju:

var logger = LoggerManager.GetLogger()

I to wszystko, potem użytkownik może wołać sobie metody tego loggera według potrzeb.

Jeśli chodzi o testowanie, to główne co można u Ciebie testować jednostkowo jest StringService (które wypadałoby jakoś sensowniej nazwać, np. LogEntryFormatter). I ta klasa nie potrzebuje do niczego żadnego interfejsu. Podobnie jak nie potrzebny jest IFileService.
Powinieneś mieć dwa zestawy testów:

  • jednostkowe (tylko przetwarzanie LogModel w string realizowane obiecnie przez StringService, które nie wymaga żadnych mocków, kontenerów IoC ani nawet DI w ogóle.
  • integracyjne (czyli LoggerManager wczytuje konfigurację, tworzy ParamsLogger, ten zapisuje coś faktycznie do pliku).

Jedyny obecnie sensowny interfejs to IParamsLogger, bo użytkownik biblioteki (jeśli bardzo chce, bo to i tak nie ma sensu) może sobie w testach zastąpić pustym mockiem, który nie będzie śmiecił po dysku.
Jeśli chodzi o abstrakcje, których tu faktycznie brakuje, to:
ITarget - bo logger powinien móc pisać do róznych miejsc, nie tylko do plików na dysku.
IFormatter, bo może fajnie byłoby móc generować JSONa albo XMLa, a nie tylko stringa oddzielonego |.


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."
edytowany 1x, ostatnio: somekind, 2019-09-04 12:57

Pozostało 580 znaków

2019-09-04 13:50
0
somekind napisał(a):

(...) Jeśli chodzi o testowanie, to główne co można u Ciebie testować jednostkowo jest StringService (...)

A co z testowaniem ParamsLogger? Jakbym potworzył makiety dla serwisów, to mógłbym publiczną metodę przetestować.

Co do ITarget oraz IFormatter, to w przyszłości może rozwinę bibliotekę, jak najdzie mnie taka potrzeba.

P.S. klasa wczytuje konfigurację z pliku, choć rozwiązałem to na trochę innej zasadzie, ale Twój pomysł ma chyba większy potencjał. Jak pamiętam NLog i log4net chyba podobnie tworzą instancję.

edytowany 2x, ostatnio: bakunet, 2019-09-04 13:58

Pozostało 580 znaków

2019-09-04 17:01
bakunet napisał(a):
somekind napisał(a):

(...) Jeśli chodzi o testowanie, to główne co można u Ciebie testować jednostkowo jest StringService (...)

A co z testowaniem ParamsLogger? Jakbym potworzył makiety dla serwisów, to mógłbym publiczną metodę przetestować.

Ja tam widzę większy problem - jedna klasa ParamsLogger ma w sobie odpowiedzialność odczytu konfiguracji, skonfigurowania samej siebie, dodawania kolejnych wpisów loga, jak i połowę logiki związanej z zapisywaniem do pliku. Na Twoim miejscu bym to poprawił, do wszystkiego powinny być oddzielne klasy, a ParamsLogger nie powinien mieć zwłaszcza logiki specyficznej do operacji dyskowych.

Generalnie prosta architektura ułatwia testowanie. No i trzeba się zastanowić - co takiego konkretnie chcesz w ParamsLogger testować, czego obecnie nie pokryją testy integracyjne?


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."
edytowany 2x, ostatnio: somekind, 2019-09-04 17:03

Pozostało 580 znaków

2019-09-05 01:27
0
somekind napisał(a):

Ja tam widzę większy problem - jedna klasa ParamsLogger ma w sobie odpowiedzialność odczytu konfiguracji, skonfigurowania samej siebie, dodawania kolejnych wpisów loga, jak i połowę logiki związanej z zapisywaniem do pliku. (...)

Przecież ParamsLogger korzysta z serwisów:

  • _fileService - zapisuje string
  • _stringService - zwraca string do zapisania
  • _configService - zwraca konfigurację

Co tak naprawdę ParamsLogger robi, to:

  • inicjalizacja
  • tworzy obiekt MethodBase ze StackTrace
  • obsługuje nieobsłużone wyjątki
    Reszta jest przyjmowana z serwisów.

Pozostało 580 znaków

2019-09-05 02:01
1

No właśnie o to mi chodzi, że ParamsLogger robi zbyt wiele. https://en.wikipedia.org/wiki/Single_responsibility_principle


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0