Czy dobrą praktyką jest przeprowadzanie testów jednostkowych w hakach kontroli wersji?


43

Z technicznego punktu widzenia możliwe jest dodanie niektórych zaczepów przed / po naciśnięciu, które będą uruchamiać testy jednostkowe przed zezwoleniem na połączenie określonego zatwierdzenia ze zdalną domyślną gałęzią.

Moje pytanie brzmi - czy lepiej jest utrzymywać testy jednostkowe w potoku kompilacji (w ten sposób wprowadzając uszkodzone commits do repo) czy lepiej po prostu nie dopuścić do wystąpienia „złych” commits.

Zdaję sobie sprawę, że nie ograniczam się do tych dwóch opcji. Na przykład mogę zezwolić wszystkim zatwierdzeniom na rozgałęzienie i przetestowanie przed wypchnięciem scalenia zatwierdzenia do repo. Ale jeśli musisz wybierać między dokładnie tymi dwoma rozwiązaniami, które wybierzesz i z jakich dokładnie powodów?


Odpowiedzi:


35

Nie, nie z dwóch powodów:

Prędkość

Zatwierdzenia powinny być szybkie. Na przykład zatwierdzenie, które zajmuje 500 ms., Jest zbyt wolne i zachęci programistów do dokonywania go oszczędniej. Biorąc pod uwagę, że w każdym projekcie większym niż Hello World będziesz mieć dziesiątki lub setki testów, uruchomienie ich podczas wstępnego zatwierdzenia potrwa zbyt długo.

Oczywiście sytuacja się pogarsza w przypadku większych projektów z tysiącami testów, które trwają kilka minut na architekturze rozproszonej lub tygodnie lub miesiące na jednej maszynie.

Najgorsze jest to, że niewiele można zrobić, aby przyspieszyć. Małe projekty w języku Python, które, powiedzmy, setki testów jednostkowych, trwają co najmniej sekundę na przeciętnym serwerze, ale często znacznie dłużej. W przypadku aplikacji C # będzie to średnio cztery-pięć sekund ze względu na czas kompilacji.

Od tego momentu możesz albo zapłacić dodatkowe 10 000 USD za lepszy serwer, co skróci czas, ale niewiele, albo uruchomić testy na wielu serwerach, co tylko spowolni.

Obie opłacają się dobrze, gdy masz tysiące testów (a także testów funkcjonalnych, systemowych i integracyjnych), co pozwala uruchomić je w ciągu kilku minut zamiast tygodni, ale to nie pomoże ci w przypadku małych projektów.

Zamiast tego możesz:

  • Zachęć deweloperów do przeprowadzenia testów silnie związanych z kodem, który zmodyfikowali lokalnie przed zatwierdzeniem. Prawdopodobnie nie mogą przeprowadzić tysięcy testów jednostkowych, ale mogą przeprowadzić pięć-dziesięć z nich.

    Upewnij się, że znalezienie odpowiednich testów i ich uruchomienie jest w rzeczywistości łatwe (i szybkie). Na przykład program Visual Studio jest w stanie wykryć, na które testy mogą mieć wpływ zmiany wprowadzone od czasu ostatniego uruchomienia. Inne IDE / platformy / języki / frameworki mogą mieć podobną funkcjonalność.

  • Zachowaj zatwierdzenie tak szybko, jak to możliwe. Egzekwowanie reguł stylu jest OK, ponieważ często jest to jedyne miejsce, aby to zrobić, a ponieważ takie kontrole są często niezwykle szybkie. Wykonywanie analizy statycznej jest prawidłowe, gdy tylko będzie działać szybko, co rzadko się zdarza. Uruchomienie testów jednostkowych jest nieprawidłowe.

  • Przeprowadź testy jednostkowe na serwerze Continuous Integration.

  • Upewnij się, że programiści są automatycznie informowani, kiedy zepsują kompilację (lub gdy testy jednostkowe się nie powiodą, co jest praktycznie tym samym, jeśli weźmiesz pod uwagę kompilator jako narzędzie sprawdzające niektóre z możliwych błędów, które możesz wprowadzić do kodu).

    Na przykład przejście do strony internetowej w celu sprawdzenia ostatnich kompilacji nie jest rozwiązaniem. Powinny być one automatycznie informowane . Wyświetlanie wyskakującego okienka lub wysyłanie wiadomości SMS to dwa przykłady tego, w jaki sposób mogą zostać poinformowani.

  • Upewnij się, że programiści rozumieją, że przerwanie kompilacji (lub nieudane testy regresji) nie jest w porządku, a jak tylko to się stanie, ich najwyższym priorytetem jest naprawa. Nie ma znaczenia, czy pracują nad funkcją o wysokim priorytecie, którą ich szef poprosił o dostarczenie na jutro: kompilacja się nie powiodła, powinni ją naprawić.

Bezpieczeństwo

Serwer hostujący repozytorium nie powinien uruchamiać niestandardowego kodu, takiego jak testy jednostkowe, szczególnie ze względów bezpieczeństwa. Czy te powody zostały już wyjaśnione w module CI na tym samym serwerze GitLab?

Z drugiej strony, jeśli twoim pomysłem jest uruchomienie procesu na serwerze kompilacji z haka przed zatwierdzeniem, spowolni to jeszcze bardziej zatwierdzenia.


4
Zgadzam się, po to jest serwer kompilacji. Kontrola źródła służy do zarządzania kodem źródłowym, a nie do upewnienia się, że aplikacja działa.
Matthew

4
W skrajnych przypadkach odwet może być niezbędnym narzędziem powiadamiania przy zerwaniu kompilacji.

37
Pół sekundy jest zbyt wolne? To kropla w koszyku w porównaniu do ostatniego spojrzenia na to, co się popełnia, a następnie wymyślenie i wpisanie odpowiedniego komentarza do zatwierdzenia.
Doval

5
@Doval: różnica polega na tym, że kiedy rzucisz ostatnie spojrzenie lub pomyślisz o komentarzu, jesteś proaktywny , a więc nie czekasz. Liczy się nie czas, który spędzasz przed wpisaniem ostatniej postaci w IDE, i czas, kiedy możesz zacząć pisać ponownie po zakończeniu zatwierdzenia, ale ile czekasz. Dlatego kompilatory powinny być szybkie; nie ma znaczenia, że ​​spędzasz znacznie więcej czasu na czytaniu i pisaniu kodu, ponieważ kiedy to robisz, nie czekasz, podczas gdy podczas kompilacji kodu masz pokusę przejścia na inną aktywność.
Arseni Mourzenko

10
@ Thomas: nie chodzi o rozrywkę, ale o irytację. W ten sam sposób 100 ms. „ma wymierny wpływ” na sposób korzystania z witryny przez osoby. Ten sam wzór tutaj: 100 ms. to nic w porównaniu z czasem spędzonym na oglądaniu filmu na YouTube lub uruchamianiu komputera: świadomie nie zauważysz różnicy między 600 ms. i 700 ms. opóźnienie. Ale nieświadomie ma to wpływ na twoje zachowanie. W ten sam sposób nieco wolniejsze zobowiązania zniechęcają cię do wczesnego i częstego angażowania się.
Arseni Mourzenko

41

Pozwól mi być tym, który nie zgadza się z moimi kolegami.

Jest to znane jako Gated Check-in w świecie TFS i spodziewam się gdzie indziej. Podczas próby odprawy do oddziału z bramkowanym odprawą zestaw półek jest wysyłany do serwera, co zapewnia, że ​​zmiany zostaną skompilowane, a testy jednostkowe określone (czytaj: wszystkie) przejdą pomyślnie. Jeśli nie, informuje cię, że jesteś złą małpką, która złamała kompilację. Jeśli tak, zmiany przechodzą w kontrolę źródła (tak!).

Z mojego doświadczenia wynika, że ​​odprawy bramkowe są jednym z najważniejszych procesów udanego testowania jednostkowego - a co za tym idzie - jakości oprogramowania.

Dlaczego?

  • Ponieważ bramkowane odprawy zmuszają ludzi do naprawiania zepsutych testów. Gdy tylko zepsute testy stają się czymś, co ludzie mogą zrobić, a nie muszą , stają się pozbawieni priorytetów przez leniwych inżynierów i / lub nachalnych ludzi biznesu.
    • Im dłużej test jest przerywany, tym trudniej (i drożej) jest to naprawić.
  • Ponieważ ludzie powinni uruchamiać testy, a nie muszą je uruchamiać, a testy są omijane przez leniwych / zapominających inżynierów i / lub nachalnych ludzi biznesu.
  • Ponieważ gdy tylko testy jednostkowe wpływają na czas zatwierdzania, ludzie naprawdę zaczynają dbać o ich testy jednostkowe . Prędkość ma znaczenie. Odtwarzalność ma znaczenie. Niezawodność ma znaczenie. Izolacja ma znaczenie.

I oczywiście jest taka korzyść, jaką przedstawiłeś pierwotnie - kiedy masz bramę odpraw i solidny zestaw testów, każdy zestaw zmian jest „stabilny”. Zapisujesz cały ten narzut (i potencjalny błąd) „kiedy była ostatnia dobra wersja?” - wszystkie kompilacje są wystarczająco dobre, aby się rozwijać.

Tak, zbudowanie i uruchomienie testów zajmuje trochę czasu. Z mojego doświadczenia wynika, że ​​5-10 minut na dobrą aplikację C # i ~ 5k testów jednostkowych. I nie obchodzi mnie to. Tak, ludzie powinni często się zameldować. Ale powinni również często aktualizować swoje zadania, sprawdzać pocztę elektroniczną lub napić się kawy lub dziesiątek innych „niedziałających nad kodem” rzeczy, które składają się na pracę inżyniera oprogramowania. Sprawdzanie złego kodu jest znacznie droższe niż 5-10 minut.


3
+1 Chcę dodać, że wiele projektów open source ma wyraźne rozróżnienie między wkładem a zobowiązaniem. Przyczyny tego są bardzo podobne do tego, dlaczego istnieją zameldowane zameldowania.
JensG

Jednym ze znaczących problemów z bramkowanym odprawą jest to, że hamuje wspólne rozwiązywanie problemów trudnego kodu. Ma to jeszcze większe znaczenie w przypadku rozproszonych zespołów.
CuriousRabbit

2
@CuriousRabbit - jak się masz? Ludzie rzadko popełniają wspólnie, nawet jeśli pracują wspólnie. W przeciwnym razie półki lub niedatowane gałęzie zadań działają do tego dobrze, nie przeszkadzając reszcie zespołu.
Telastyn

@ Telastyn– Shelvesets to dla mnie nowy termin. Rozumiem, że jest to opcja MS VS, która nie jest opcją dla dużej liczby projektów. W dziedzinie tworzenia aplikacji mobilnych VS nie jest odtwarzaczem.
CuriousRabbit

3
@CuriousRabbit - naprawdę? Zespół rozłożony na 17 godzin bardziej troszczy się o czekanie 10 minut na zatwierdzenie niż na możliwość zepsutej kompilacji, gdy przestępca śpi? To wydaje się ... mniej niż optymalne.
Telastyn

40

Zatwierdzenia powinny działać szybko. Kiedy zatwierdzam jakiś kod, chcę, aby został przekazany na serwer. Nie chcę czekać kilka minut na uruchomienie zestawu testów. Jestem odpowiedzialny za to, co pcham na serwer i nie potrzebuję nikogo, kto opiekowałby się mną przy pomocy haków zatwierdzania.

To powiedziawszy, kiedy dostanie się na serwer, należy go przeanalizować, przetestować i zbudować natychmiast (lub w krótkim czasie). Ostrzegłoby mnie to o tym, że testy jednostkowe są zepsute lub nie zostały skompilowane, albo zrobiłem bałagan pokazany przez dostępne narzędzia analizy statycznej. Im szybciej to się dzieje (kompilacja i analiza), tym szybciej moja informacja zwrotna i tym szybciej jestem w stanie to naprawić (myśli nie wyszły całkowicie z mojego mózgu).

Więc nie, nie umieszczaj testów i tym podobnych w hakach zatwierdzania na kliencie. Jeśli musisz, umieść je na serwerze po zatwierdzeniu (ponieważ nie masz serwera CI) lub na serwerze kompilacji CI i odpowiednio ostrzeż mnie o problemach z kodem. Ale nie blokuj przede wszystkim dokonywania zmian.

Powinienem również zauważyć, że przy niektórych interpretacjach Test Driven Development należy sprawdzić test jednostkowy, który najpierw się psuje . Pokazuje to i dokumentuje występowanie błędu. Późniejszym meldowaniem będzie kod, który naprawia test jednostkowy. Zapobieganie meldowaniu się do momentu zdania testów jednostkowych zmniejszyłoby efektywną wartość sprawdzania w teście jednostkowym, który nie udokumentuje problemu.

Powiązane: Czy powinienem mieć testy jednostkowe znanych usterek? i Jaka jest wartość sprawdzania w nieudanych testach jednostkowych?


2
Commits should run fast.jakie są z tego korzyści? Jestem ciekawy, ponieważ obecnie używamy bramkowanych czeków. Zazwyczaj moje meldowanie to kumulacja około godziny pracy, więc 5-minutowe oczekiwanie nie jest wielką sprawą. W rzeczywistości odkryłem, że jego zestyk czasy, kiedy jestem w pośpiechu, że build walidacji jest najbardziej przydatna do połowu głupie błędy (wskutek pośpiechu)
Justin

1
@ Justin Pięć minut oczekiwania to pięć minut oczekiwania bez względu na to, gdzie się znajduje. One nie powinny trzeba wyrwać miecze NERF za każdym razem gdy zatwierdzimy. I często zdarza mi się, że dzielę godzinę pracy na wiele zatwierdzeń, z których każda jest jednostką pojęciową - „zatwierdz kod resztowy usługi”, a następnie „zatwierdz kod dla obsługiwanej strony klienta” jako dwa osobne zatwierdzenia (nie wspominając o poprawce css jako kolejnym zatwierdzeniu). Jeśli uruchomienie każdego z nich zajęło 5 minut, 1/6 mojego czasu poświęcana jest na testy. Doprowadziłoby to do większych zatwierdzeń

5 minut czekać na uruchomienie testów jednostkowych? Wydaje mi się, że projekty, nad którymi pracują, powinny zostać podzielone na mniejsze elementy.
user441521,

@ Justin catching silly mistakes (as a result of rushing)dokładnie. Pośpiech w ogóle jest złą praktyką w inżynierii oprogramowania. Robert C. Martin zaleca pisanie kodu takiego jak operacja youtube.com/watch?v=p0O1VVqRSK0
Jerry Joseph

10

Zasadniczo uważam, że sensowne jest zapobieganie wprowadzaniu zmian w głównej linii, które przerywają kompilację. Oznacza to, że proces wprowadzania zmian w głównej gałęzi repozytorium powinien wymagać upewnienia się, że wszystkie testy nadal przebiegają pomyślnie. Przerwanie kompilacji jest po prostu zbyt kosztowne ze względu na stracony czas, aby wszyscy inżynierowie w projekcie mogli zrobić cokolwiek innego.

Jednak szczególne rozwiązanie haków zatwierdzających nie jest dobrym planem.

  1. Deweloper musi czekać na uruchomienie testów podczas zatwierdzania. Jeśli programista musi czekać na swoim stanowisku roboczym na zaliczenie wszystkich testów, tracisz cenny czas inżyniera. Inżynier musi być w stanie przejść do następnego zadania, nawet jeśli będzie musiał wrócić, ponieważ testy zakończyły się niepowodzeniem.
  2. Programiści mogą chcieć zatwierdzić uszkodzony kod w oddziale. W większym zadaniu wersja dla programistów kodu może spędzać dużo czasu bez przejścia w stan przejściowy. Oczywiście połączenie tego kodu z linią główną byłoby bardzo złe. Ale ważne jest, aby deweloper mógł nadal używać kontroli wersji do śledzenia swoich postępów.
  3. Czasami istnieją dobre powody, aby pominąć proces i pominąć testy.

2
# 1 można obejść, umożliwiając programistom zameldowanie się w osobistym oddziale lub lokalnym repozytorium. Tylko wtedy, gdy deweloper chce gdzieś swój kod, inni programiści mogą go zobaczyć, że testy jednostkowe muszą zostać uruchomione. Podobnie jak w przypadku # 1, # 2 jest usuwany tylko przez haki do gałęzi głównej. # 3 jest eliminowany przez fakt, że A) Dowolną taką funkcję można wyłączyć, nawet jeśli jest to kłopot (i powinien być kłopot) i B) Indywidualne testy jednostkowe mogą zostać wyłączone.
Brian

@Brian, zgadzam się całkowicie, możesz to zrobić. Ale próba zablokowania haka zatwierdzania po stronie serwera nie zadziała.
Winston Ewert

słuszne uwagi. Breaking the build is simply too costly in terms of lost time for all engineers on the projectSugerowałbym użycie pewnego rodzaju narzędzia do powiadamiania o kompilacji, aby uniknąć sytuacji, w której wszyscy inżynierowie tracą czas na każdą zepsutą kompilację
Jerry Joseph,

3

Nie, nie powinieneś, jak zauważyły ​​inne odpowiedzi.

Jeśli chcesz mieć bazę kodu, która gwarantuje brak nieudanych testów, możesz zamiast tego rozwijać się w gałęziach funkcji i pobierać żądania do master. Następnie możesz zdefiniować warunki wstępne dla akceptacji tych żądań ściągania. Zaletą jest to, że możesz naciskać naprawdę szybko, a testy działają w tle.


2

Konieczność oczekiwania na udaną kompilację i testy przy każdym zatwierdzeniu do głównej gałęzi jest naprawdę okropna, myślę, że wszyscy się z tym zgadzają.

Istnieją jednak inne sposoby osiągnięcia spójnej gałęzi głównej. Oto jedna sugestia, nieco podobna do bramkowanych odpraw w TFS, ale którą można uogólnić do dowolnego systemu kontroli wersji z rozgałęzieniami, chociaż najczęściej używam terminów git:

  • Posiadaj gałąź pomostową, do której możesz jedynie zatwierdzać połączenia między gałęziami programistów i gałęziami głównymi

  • Skonfiguruj hook, który uruchamia lub umieszcza w kolejce kompilację i testy zatwierdzeń wykonanych w gałęzi pomostowej, ale nie powoduje to oczekiwania

  • Po udanej kompilacji i testach automatycznie uruchom główną gałąź , jeśli jest aktualna

    Uwaga: nie scalaj automatycznie z gałęzią główną, ponieważ testowane scalanie, jeśli nie scalanie do przodu z punktu widzenia gałęzi głównej, może się nie powieść po scaleniu z gałęzią główną z zatwierdzeniami pomiędzy

W konsekwencji:

  • Zabroń ludziom angażowania się w główny oddział, automatycznie, jeśli możesz, ale także w ramach oficjalnego procesu, jeśli istnieje luka lub jeśli nie jest to technicznie wykonalne

    Przynajmniej możesz upewnić się, że nikt nie zrobi tego przypadkowo lub bez złośliwości, gdy będzie to podstawowa zasada. Nigdy nie powinieneś próbować tego robić.

  • Będziesz musiał wybrać między:

    • Pojedyncza gałąź przemieszczania, która w przeciwnym razie spowoduje udane scalenie, faktycznie się nie powiedzie, jeśli poprzednie, jeszcze nieskompilowane i nie przetestowane scalenie nie powiedzie się

      Przynajmniej będziesz wiedział, które scalenie nie powiodło się, i ktoś może to poprawić, ale połączenia między nimi nie są łatwo identyfikowalne (przez system kontroli wersji) wyników dalszych kompilacji i testów.

      Możesz spojrzeć na adnotację pliku (lub obwinianie), ale czasami zmiana pliku (np. Konfiguracji) spowoduje błędy w nieoczekiwanych miejscach. Jest to jednak dość rzadkie wydarzenie.

    • Wiele gałęzi pomostowych, które pozwolą na bezproblemowe pomyślne scalanie, aby dostać się do gałęzi głównej

      Nawet w przypadku, gdy w jakiejś innej gałęzi pomostowej nie występują sprzeczne ze sobą scalenia. Identyfikowalność jest nieco lepsza, przynajmniej w przypadku, gdy jedno połączenie nie spodziewało się wpływającej zmiany od innego połączenia. Ale znowu jest to wystarczająco rzadkie, aby nie martwić się o to codziennie lub co tydzień.

      Aby przez większość czasu mieć nieskonfliktowe połączenia, ważne jest rozsądne rozdzielenie gałęzi pomostowych, np. Na zespół, na warstwę lub na komponent / projekt / system / rozwiązanie (jakkolwiek to nazwiesz).

      Jeśli główna gałąź została w międzyczasie przekazana do innego scalenia, musisz scalić ponownie. Mamy nadzieję, że nie jest to problem z połączeniami nie powodującymi konfliktów lub z bardzo niewielką liczbą konfliktów.

W porównaniu z bramkowanymi odprawami, zaletą jest to, że masz gwarancję działającego oddziału głównego, ponieważ główny oddział może tylko iść do przodu, a nie automatycznie łączyć zmiany z tym, co zostało popełnione pomiędzy. Zatem trzecia kwestia to zasadnicza różnica.


Ale celem posiadania działającej gałęzi nie jest (zwykle) zapewnienie stabilnego wydania, ale ograniczenie rażenia spowodowanego przez programistów pracujących razem w tym samym kodzie.
Telastyn

Nie, jeśli masz ogromne repo z dużymi, rozproszonymi na całym świecie zespołami. Na przykład Microsoft stosuje podobne, bardziej warstwowe podejście w systemie Windows.
acelent

2

Wolę, aby „zdane testy jednostkowe” były bramą do przesyłania kodu. Aby jednak zadziałało, będziesz potrzebować kilku rzeczy.

Będziesz potrzebował struktury kompilacji, która buforuje artefakty.

Będziesz potrzebował środowiska testowego, które buforuje status testu z (udanych) uruchomień testowych, z dowolnym danym artefaktem (ami).

W ten sposób zameldowania z pozytywnymi testami jednostkowymi będą szybkie (kontrola krzyżowa ze źródła do artefaktu, który został zbudowany, gdy programista sprawdził testy przed odprawą), osoby z nieudanymi testami jednostkowymi zostaną zablokowane, a programiści, którzy wygodnie zapomnieć o sprawdzeniu kompilacji przed zatwierdzeniem. Zachęcamy do zrobienia tego następnym razem, ponieważ cykl kompilacji i testowania jest długi.


1

Powiedziałbym, że zależy to od projektu i zakresu automatycznych testów przeprowadzanych na „zatwierdzeniu”.

Jeśli testy, które chcesz uruchomić w wyzwalaczu odprawy, są naprawdę szybkie lub jeśli przepływ pracy programisty i tak wymusi trochę pracy administracyjnej, myślę, że nie powinno to mieć większego znaczenia i zmusić programistów do sprawdzania w rzeczach, które absolutnie uruchamiają najbardziej podstawowe testy. (Zakładam, że przeprowadzałbyś tylko najbardziej podstawowe testy z takim wyzwalaczem).

I myślę, że jeśli pozwala na to szybkość / przepływ pracy, dobrze jest nie wypychać zmian innym programistom, którzy nie przejdą testów - i wiesz tylko, czy zawiodą, jeśli je uruchomisz.

W pytaniu piszesz „zobowiązanie ... do zdalnej gałęzi”, co sugeruje mi, że to (a) nie jest czymś, co dev robi co kilka minut, więc małe oczekiwanie może być bardzo dobrze do przyjęcia, i (b) że po takie zatwierdzenie zmiany kodu mogą mieć wpływ na innych programistów, więc mogą być wymagane dodatkowe kontrole.

Mogę zgodzić się z innymi odpowiedziami na temat: „nie zmuszaj swoich deweloperów do kiwnięcia kciukami podczas oczekiwania” na taką operację.


1

Złamane zatwierdzenia nie powinny być dozwolone na pniu , ponieważ pień jest tym, co może wejść do produkcji. Musisz więc upewnić się, że istnieje brama, którą muszą przejść, zanim znajdą się w bagażniku . Jednak złamane zatwierdzenia mogą być całkowicie poprawne w repo, o ile nie ma ich na pniu.

Z drugiej strony wymaganie od deweloperów czekania / naprawiania problemów przed wprowadzeniem zmian do repozytorium ma szereg wad.

Kilka przykładów:

  • W TDD często zdarza się, że zatwierdza się i wypycha nieudane testy nowych funkcji przed rozpoczęciem ich wdrażania
  • To samo dotyczy zgłaszania błędów poprzez zatwierdzanie i wypychanie testu zakończonego niepowodzeniem
  • Wciśnięcie niekompletnego kodu pozwala równolegle pracować nad funkcją co najmniej 2 osobom
  • Twoja infrastruktura CI może wymagać czasu na sprawdzenie, ale programista nie musi czekać
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.