Unit testing internal classes

While working on class libraries, it’s a common and a good practice to unit test it before publishing. If you think about making implementation of interfaces hidden from other assemblies you may mark the class as internal. Unfortunately, it will make testing more difficult. Luckily there’s a way to handle it. All we need to do is finding our class library a friend. Friend assemblies can access internal types of another assembly.

I’ll present two ways of making a friend for the assembly:

  1. Use attribute [assembly: InternalsVisibleTo("FriendAssemblyName")] on any of classes
  2. Add following section to csproj file
    <ItemGroup><AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"><_Parameter1>FriendAssemblyName</_Parameter1></AssemblyAttribute></ItemGroup>

Polityka prywatności

W niniejszej polityce prywatności znajdziesz informacje czy i w jaki sposób są przetwarzane Twoje dane osobowe oraz ciastka.

Dane osobowe

Twoje dane osobowe są zbierane w przypadku gdy pozostawisz komentarz pod artykułem. Dane te nie są przekazywane przeze mnie do firm trzecich.

Gdyby się to miało zmienić to powstanie na ten temat osobny post z informacją o tym.

Gdybyś Drogi Czytelniku zapragnął usunięcia swoich danych osobowych, daj proszę znać na adres bartosz.clapa@gmail.com. Usunę wszystko co powinienem.

Pliki cookies – ciasteczka

Ciasteczka to informacje tekstowe, które są zapisywane w Twojej przeglądarce. Pozwalają na zapisanie informacji o Twoich preferencjach i ewentualnie pomóc dostosować stronę do Twoich potrzeb.

Na tej witrynie ciastka zbierają następujące informacje:

  • Dane potrzebne do statystyk odwiedzin, między innymi o czasie ostatniej wizyty, czy odwiedzający był już wcześniej na tej witrynie, urządzeniu z jakiego się łączy, lokalizacji na podstawie adresu IP, itp.

Jeśli nie wyrażasz na to zgody, wyłącz proszę obsługę cookies w używanej przez siebie przeglądarce.

 

Google Analytics

W celu analizy statystyk strony jest zainstalowany skrypt Google Analytics.

Danych osobowych nie zbiera, a mi miło jest od czasu do czasu obejrzeć ładne wykresy z liczbą odwiedzających 😉

 

Moje obowiązki a Twoje prawa w związku z danymi osobowymi

Jeśli dodałeś jakiś komentarz to przekazałeś mi swoje dane osobowe. Moim obowiązkiem, w odpowiedzi na Twoją prośbę jest przesłanie Twoich osobistych, w szczególności tych dostarczonych przez Ciebie. Gdybyś zarządał usunięcia swoich danych to oczywiście to zrobię. Dane autora i jego komentarze zostaną pozbawione cech umożliwiających identyfikację osoby za nimi strojącej. Oczywiście jeśli jakieś dane będą mi potrzebne ze względów administracyjnych, prawnych lub bezpieczeństwa to je pozostawię.

 

Podziękowania

Dziękuję autorce bloga blog.anoriell.eu za inspirację i nieświadome wpłynięcie na mobilizację by usunąć obrazek „Closed due to GDPR”.

 

 

 

Uruchamianie usług z parametrami wywołania

Ostatnio podczas konfiguracji kontenera Dockera natrafiłem na problem z instalacją i uruchamianiem własnych usług Windows. Próbowałem między innymi zainstalować Remote Debugger, aby ułatwić sobie wykorzystanie kontenerów jako środowiska developerskiego. Niestety okazało się, że parametry wywołania (np. /noauth), które działają przy ręcznym uruchomieniu aplikacji nie są akceptowane.

W związku z wspomnianym problemem postanowiłem napisać mały program, który będzie działał jako usługa i umożliwiał uruchamianie aplikacji z dowolnymi parametrami. Przy okazji przypomniałem w jaki sposób się to robi. Szczegóły poniżej.

 

Program będzie miał za zadanie uruchamiać i zamykać aplikacje, które zostaną podane w jego pliku konfiguracyjnym.

Jak w przypadku każdego nowego projektu musimy go najpierw utworzyć. W MS Visual Studio tworzymy aplikację konsolową (Console Application).

Modyfikujemy plik Program.cs tak, by znajdująca się w nim klasa dziedziczyła po klasie ServiceBase. Wymagane będzie dodanie referencji do biblioteki System.ServiceProcess oraz dopisanie linijki:

using System.ServiceProcess;

Następnie dodajemy klasy, które będą tworzyły sekcję konfiguracyjną przechowującą listę programów z parametrami, przeznaczonymi do uruchomienia podczas startu naszej usługi:

  • ServiceEntry -> pojedynczy wpis w konfiguracji
  public class ServiceEntry : ConfigurationElement
  {
    [ConfigurationProperty("Name", IsKey = true, IsRequired = true)]
    public string Name
    {
      get { return this["Name"] as string; }
      set { this["Name"] = value; }
    }

    [ConfigurationProperty("Path", IsKey = false, IsRequired = true)]
    public string Path
    {
      get { return this["Path"] as string; }
      set { this["Path"] = value; }
    }

    [ConfigurationProperty("Params", IsKey = false, IsRequired = false)]
    public string Params
    {
      get { return this["Params"] as string; }
      set { this["Params"] = value; }
    }
  }
  • ServiceEntryCollection -> kolekcja wpisów konfiguracyjnych
  public class ServiceEntryCollection : ConfigurationElementCollection
  {
    protected override ConfigurationElement CreateNewElement()
    {
      return new ServiceEntry();
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
      if (element == null)
        throw new ArgumentNullException("element");

      return ((ServiceEntry)element).Name;
    }
  }
  • ServicesToRunSection -> sekcja konfiguracyjna
  public class ServicesToRunSection : ConfigurationSection
  {
    public const string SectionName = "servicesToRun";

    [ConfigurationProperty("", IsDefaultCollection = true)]
    public ServiceEntryCollection ServiceEntryCollection
    {
      get
      {
        return this[""] as ServiceEntryCollection;
      }
    }

    public static ServicesToRunSection GetConfig()
    {
      return ConfigurationManager.GetSection(SectionName) as ServicesToRunSection ?? new ServicesToRunSection();
    }
  }

Po dodaniu zmodyfikujmy plik konfiguracyjny zgodnie ze schematem z klas.

Dodajemy informację o wykorzystywanej sekcji do pliku App.config do tagu <configSections>:

  <configSections>
    <section name="servicesToRun" type="ServiceStarter.ServicesToRunSection, ServiceStarter"/>
  </configSections>

oraz samą sekcję:

<configuration>
...
  <servicesToRun>
    <add Name="ping" Path="ping" Params="-t 8.8.8.8" /> <!-- będziemy pingować po starcie systemu -->
  </servicesToRun>
...
</configuration>

Zakończyliśmy właśnie wstępną konfigurację aplikacji do uruchomienia.

Skoro mamy już konfigurację wrócimy teraz do samej usługi.

Otwieramy plik z klasą dziedziczącą po ServiceBase.

W konstruktorze ustawiamy nazwę usługi i jej inne parametry. Dodajemy także statyczną listę otwartych procesów.

    private static List<Process> _processes;

    public ServiceStarterService()
    {
      _processes = new List<Process>();

      ServiceName = "ServiceStarter";
      CanStop = true;
      CanPauseAndContinue = false;
    }

Przeciążamy metody OnStart oraz OnStop.

W OnStart będziemy wczytywali z pliku konfiguracyjnego listę aplikacji do uruchomienia oraz je startowali.

    protected override void OnStart(string[] args)
    {
      var config = ServicesToRunSection.GetConfig();

      var data = config.ServiceEntryCollection;

      foreach (ServiceEntry entry in data)
      {
        var process = Process.Start(entry.Path, entry.Params);
        _processes.Add(process);
      }
    }

OnStop wykorzystamy do zatrzymania uruchomionych procesów oraz posprzątania po sobie.

    protected override void OnStop()
    {
      foreach (var process in _processes)
      {
        if (!process.HasExited)
          process.Close();
      }

      _processes.Clear();
    }

W metodzie Main wskazujemy, że chcemy uruchomić naszą klasę:

    static void Main(string[] args)
    {
      Run(new ServiceStarterService());
    }

Pozostało nam tylko zainstalować nową usługę. Potrzebny będzie nam do tego instalator, czyli klasa dziedzicząca po klasie Installer. Aby wykorzystać ten instalator musimy go oznaczyć atrybutem [RunInstaller(true)].

  [RunInstaller(true)]
  public class ServiceStarterServiceInstaller : Installer
  {
    public ServiceStarterServiceInstaller()
    {
      var serviceProcessInstaller = new ServiceProcessInstaller();
      var serviceInstaller = new ServiceInstaller();

      serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
      serviceProcessInstaller.Username = null;
      serviceProcessInstaller.Password = null;

      serviceInstaller.DisplayName = "ServiceStarter";
      serviceInstaller.StartType = ServiceStartMode.Automatic;

      serviceInstaller.ServiceName = "ServiceStarter";

      Installers.Add(serviceProcessInstaller);
      Installers.Add(serviceInstaller);
    }
  }

 

Po skompilowaniu usługę instalujemy przy pomocy narzędzia InstallUtil.exe. Opis narzędzie i jego użycia znajdziecie pod poniższymi linkami:

Pełen kod znajdziecie po kliknięciu w odnośnik.