Na sali są panie


Oh! Myślę, że w końcu zrozumiałem!
Być może wywołam falę emocji pisząc to, co mam do napisania; ale myślę, że w końcu zrozumiałem coś, co mnie niepokoiło.
Trzy razy w ciągu ostatnich kilku lat spotkałem się z kobietami, które poczuły się obrażone tym, co powiedziałem w czasie rozmowy, czy też wykładu.

Poniższy tekst jest luźnym tłumaczeniem wpisu z bloga Roberta Cecila Wujka Boba Martina stąd:
Proszę o komentarze, gdyby ta luźność była zbyt daleko posunięta.

Po raz pierwszy było to kilka lat temu na konferencji Rails. Wygłosiłem wówczas wykład zatytułowany: "To, co zabiło Smalltalka, może też zabić Ruby". Nazwałem wówczas język C++ językiem testosteronu, a język Java językiem estrogenu. Nie miałem nic złego na myśli. Zażartowałem jedynie na temat tego, jak poważnie programiści C++ traktują siebie samych w porównaniu z programistami Java. Jednak niektóre kobiety na widowni poczuły się obrażone i napisały o tym na Tweeterze. Szybko i publicznie przeprosiłem je, ponieważ nie chcę, aby kobiety poczuły się niemile widziane w naszej branży. Myślę, że bardzo Je potrzebujemy. Ale o tym później...
Chociaż przeprosiłem, to jednak byłem trochę zdumiony. Moje drugie ja – Tim Taylor – zapytało: „eeeuuuhhh?”



Co było obraźliwego w tym, że użyłem określenia testosteron/estrogen? Nie wiedziałem wtedy tego, ale odpowiedź była bardzo blisko. Po prostu wtedy jeszcze nie byłem jej świadom.
Kilka lat później wygłaszałem prelekcję na konferencji ACCU w Oxfordzie. Tematem było "Requiem dla C". Zacząłem mówić o COBOLu. I to spowodowało, że przyszło mi do głowy powszechnie znane zdjęcie pani admirał Grace Hopper, wynalazczyni COBOLa.



Na zdjęciu stała dumnie w mundurze oficera marynarki i z czapką na głowie. Użyłem wtedy słów "z tą słodką małą czapeczką". Chciałem je cofnąć, ale ...
Po wykładzie podeszła do mnie Emily Bache i delikatnie zasugerowała, że moja uwaga nie była zbyt pomocna dla kobiet na widowni. Przypomniała mi, że kobietom i tak jest wystarczająco trudno być programistkami w męskim towarzystwie nawet bez moich żartów o innych kobietach. Całkowicie się z nią zgodziłem i opublikowałem kolejne przeprosiny. Opublikowałem je, bo chcę, aby jak najwięcej kobiet było w naszej branży. Myślę, że bardzo Je potrzebujemy. Ale o tym później...
W tym tygodniu wykładałem na temat Clean Code w Londynie. Miałem 30 studentów, z których cztery to były kobiety. Byłem bardzo zadowolony, ponieważ był to jeden z najwyższych wskaźników udziału kobiet, jakie miałem na zajęciach. Wykład się udał, a po trzech dniach, kiedy odbyłem je wszystkie, przeczytałem ich ocenę.
Jedna z kobiet napisała, że ​​podobały jej się zajęcia i wiele się z nich nauczyła, ale potem dodała; "były bardziej ukierunkowane na mężczyzn, co sprawiło, że czułam się nieswojo". Tim Taylor: “Eeeuuuhhh?”



Co ja takiego powiedziałem? Nawet teraz nie mam pojęcia, co takiego mogło mi się wymknąć. Nie wiem, co tamta kobieta mogła mieć na myśli. Ale w tym samym tygodniu wydarzyło się coś jeszcze, co wiele wyjaśniło.
Zdarzeniem, o którym chcę opowiedzieć, było fiasko na PyCon, w którym uczestniczyli Adria Richards, Alex Reid, Playhaven i SendGrid. Jeśli nie masz pojęcia o co chodzi, to w blogu Amandy Blum znajdziesz odpowiednie wytłumaczenie. Nie będę się rozwodził na temat tego, kto w tym wszystkim miał rację, wystarczy powiedzieć, że prawdziwymi winowajcami okazali się śmierdziele dokonujący ataki DDOS, a także tweetujący osobiste obelgi i groźby karalne.
Więc co z tego wynika? Cóż, opowiem najpierw kilka historii:
Kiedy byłem w ósmej klasie, przyszedł do nas nowy uczeń. Został wyrzucony z dawnej szkoły w dzielnicy o bardzo złej reputacji, więc natychmiast stał się bohaterem. Prawdziwy "zakapior". Najpierw pozdrawiał wszystkich uśmiechając się olśniewająco, a potem kusząco wskazywał kciukiem na rozporek. Gdy nieuchronnie spojrzałeś w dół, aby podążać za jego wskazującym gestem, mówił: "Ale sztuka!". Ha, ha, ha. Hi, hi, hi. Ho, ho, ho. Nic bardziej nie może rozświetlić dnia, jak humor nastoletniego chłopca.

Dwadzieścia lat temu pracowałem, jako główny architekt oprogramowania w startupie zajmującym się zarządzaniem siecią. Pewnego dnia poszedłem do toalety, gdzie dwaj faceci w garniturach załatwiali swoje potrzeby przy pisuarach. Kiedy skończyli, jeden z nich rzekł do drugiego: "Uważaj, jak strzepujesz tę rosę ze swojego kwiatka!" Ten drugi odpowiedział: "Jeszcze tylko dwa potrząśnięcia i jest gotowy do zabawy!" Ha, ha, ha. Hi, hi, hi. Ho, ho, ho. Nic bardziej nie rozświetla dnia, jak pisuarowy humor trzydziestoparolatka.

Kilka lat temu pewnego wieczoru oglądałem Ostry Dyżur, gdzie piękna dziewczyna radziła swojej równie pięknej koleżance, żeby rzuciła swojego chłopaka, ponieważ ten nie sprostał oralnemu wyzwaniu i nie chciał posunąć się na południe od równoleżnika 32. Nie chciał też pożywić się z Y. Ha, ha, ha. Hi, hi, hi. Ho, ho, ho. Nic bardziej nie rozświetla dnia, jak twórcy niegdyś wielkiego telewizyjnego show, którzy ulegają pokusie, aby użyć niesmacznego pisuarowego humoru nastolatków.

Moja żona została fanem serialu Chirurdzy. Widziałem kilka odcinków tej produkcji, ale nie byłem zachwycony. Było tam wyłącznie o zdradzie, pieprzeniu w każdym nadającym się do tego miejscu, przypinaniu majtek do tablic ogłoszeń, i o tym, jak nauczyć byłego chłopaka, aby zadowolił nową, lesbijską kochankę. Ha, ha, ha. Hi, hi, hi. Ho, ho, ho. Nic tak nie rozświetla dnia, jak fekalny humor ośmiolatka.

Wszystkie te rzeczy mają jedną wspólną cechę. Niesmaczny humor. Uważałam, że wszystko to jest obraźliwe dla mojej inteligencji, obraźliwe dla moich wartości i po prostu obraźliwe – ze względu na kontekst. Takie rzeczy nie powinny mieć miejsca w szkole, w toalecie, ani w telewizji w moim salonie. Powinno się zostawić je w szatni, która jest najlepszym dla nich miejscem.
Czy stworzyliśmy środowisko szatni w branży oprogramowania? Czy do tego stopnia zdominowane ono zostało przez mężczyzn, że przekształciło się w miejsce, gdzie mężczyźni rozluźniają się i opowiadają żarty o puszczaniu bąków i fiutach, aby łaskotać swoje niedojrzałe ego? Czy kiedy programiści rodzaju męskiego przebywają razem, czują się tak swobodnie, że mogą porzucić wszelkie zasady dobrych obyczajów?
Co stałoby się, gdyby role zostały odwrócone? Jak by to było, gdyby kobiety zdominowały softwarową branżę, a my bylibyśmy tymi, którzy starają się do niej wedrzeć? Panowie, czy możecie sobie wyobrazić, jak byłoby nam nieswojo, gdyby wszystkie kobiety ciągle i otwarcie mówiły o tamponach, skurczach, drożdżowych infekcjach, zdradach, byciu zdradzanym, zapaleniach pochwy, itd.? Nie wiem jak wy, ale ja czułbym się wykluczony. I nie można byłoby się ukryć, bo kobiety byłyby wszędzie. Pragnąłbym wtedy, żeby tego rodzaju rozmowy odbywały się tylko w damskiej szatni. Chciałbym wówczas, żeby pamiętały o tym, że na sali są panowie.
Panowie, być może powinniśmy pamiętać, że na sali są panie.
To ostatnie zdanie może wpędzić mnie w kłopoty, ponieważ zbyt wiele nieuczciwych opinii było wypowiedzianych pod pretekstem tego, że są na sali są panie. Dowiedziałem się o tym z filmu o nazwie Niebezpieczna Piękność. Opowieść była o pięknej i młodej kurtyzanie w XVI-wiecznej Wenecji. Ważni ludzie z miasta zabierali ją na wszystkie spotkania polityczne i biznesowe, pozwalając jej poznawać ich skryte plany. Owi mężczyźni trzymali swoje żony z daleka, ponieważ nie chcieli rozmawiać o takich rzeczach gdy na sali są panie. Więc, aby dowiedzieć się, co się naprawdę dzieje z ich mężami i miastem, żony konsultowały się z kurtyzaną; i one używały określenia, które bardzo mi się spodobało. Mówiły, że zostały skazane na ciągłe lekceważenie.
Ciągłe lekceważenie. To brzmi jak więzienie. To brzmi jak piekło. Jeżeli to oznacza “być traktowanym jak dama”, to do diabła z tym.
Myślę, że to wyjaśnia inne wydarzenie, jakie było moim udziałem przy okazji uczestnictwa w konferencji Midwestern Ruby kilka lat temu. Zanim wygłosiłem końcową mowę przewodnią na temat Architektury, siedziałem na widowni, słuchając młodego człowieka, który wygłaszał bardzo interesującą prezentację na temat GitHuba. Niestety byłem udręczony tym, że w każdym jej zdaniu padało grube słowo na “ka”. I było “ku” tu i “ku” tam i “ku” wszędzie, mówię Wam. Na koniec opowiadał o kodzie, który napisał, gdy był ostro nawalony.
Wtedy ktoś wstał, wygłosił kolejną mowę i wspomniał, że jest szczęśliwy, ponieważ na widowni jest tak wiele kobiet, których potrzebujemy w naszej branży. (Miał rację ... ale o tym później ...).
Wtedy przyszła moja kolej. Zacząłem : "Jeśli chcemy mieć więcej kobiet w naszej branży, to musimy zważać na słowa na "ka" i "cha". Kilku mężczyzn z sali zgodziło się ze mną i rozległy się brawa. Ale pewna młoda kobieta wstała i krzyknęła: "Całkowicie się nie zgadzam, kurwa!"
Tim Taylor: "O co chodzi?"



Teraz uważam, że w końcu to zrozumiałem. Powiedziała mniej więcej tak:
"Nie oczekuję, abyś mnie wyróżniał, kiedy mówisz o sprawach zawodowych, bo nie zgodzę się na wieczne lekceważenie . Jeśli używasz grubych słów na “ka” w profesjonalnym kontekście, to nie powstrzymuj się tylko dlatego, że jestem kobietą. Pamięta jednak, że jestem kobietą i nie chcę uczestniczyć w tym, co mówią i robią mężczyźni. Takie rzeczy możesz mówić w męskiej toalecie, ale nie w mojej obecności, bo mnie to doprowadza do szału.”
Teraz, aby spełnić to, co obiecałem.
Miałem przyjemność spędzić godzinkę z Desi McAdam (@desi) jednego wieczoru, podczas, gdy implementowała jedną funkcję. Powiedziałem jej, że dla mnie, sprawianie żeby program działał jest jak podbój - zabijanie bestii i przynoszenie mięsa do domu. Odpowiedziała, że dla niej było to raczej odżywcze doświadczenie wzrostu pomagające oprogramowaniu przyjść na świat. Tim Taylor: "Eeeuuhhhh?".



Miałem trochę czasu, aby o tym pomyśleć. Więc kiedy myślę o kodzie, mam na myśli zabijanie robactwa i ugniatanie kodu według moich zamierzeń. Jestem panem. To mój niewolnik. Mam moc, aby sprawiać żeby był taki, jaki chcę. Dla mnie to jest mega odjazd.
Zastanawiam się, czy to w części nie tłumaczy problemu z rzemiosłem i profesjonalizmem, który ma nasza branża. Dlaczego tak dużo naszego oprogramowania jest tak źle zrobiona? Czy to dlatego, że traktujemy je jako podbój? Czy to dlatego, że niezbyt wystarczająco je pielęgnujemy?
Nie zrozumcie mnie źle, dla mnie to zawsze będzie podbój. Zawsze będę panem mojego kodu. To jestem ja. Ale być może to nie wystarczy, aby przekształcić naszą pracę nad oprogramowaniem w prawdziwy zawód. Może wymaga ona również innej postawy.
Wszakże w życiu osobistym wiem, że moje najgorsze decyzje podjąłem bez udziału żony. Żona uzupełnia mnie na wiele różnych sposobów. Bez niej byłbym cieniem mężczyzny, który przemierza dwuwymiarową przestrzeń. Moje relacje z nią sprawiły, że z chłopca przeobraziłem się w mężczyznę. Pomogła mi dorosnąć.
Nasza branża też musi dorosnąć. Być może powodem, dla którego mamy tyle problemów z dojrzewaniem jest to, że nie mamy wokół siebie wystarczająco dużo kobiet, które pomogłyby nam przejść proces dorastania. Ich postawy, sposoby myślenia, różniące się od naszych, mogą być czymś, czego potrzebujemy, aby udoskonalić naszą branżę, zawód oraz rzemiosło. Wydaje mi się, że potrzebujemy kobiet, aby nasza profesja stała się profesją.
Musimy sprawić, aby kobiety poczuły się mile widziane w naszym towarzystwie. Powinniśmy również zdać sobie sprawę z tego, że cała branża oprogramowania komputerowego nie jest naszą prywatną szatnią. Musimy traktować kobiety jak profesjonalnych partnerów, zważając jednocześnie na to, że stale są między nami obecne.
Oczywiście, jestem tylko Timem Taylorem, który rozmawia przez ogrodzenie z Wilsonem.



Drogie panie, czy ująłem to dobrze?



Powyższy tekst jest luźnym tłumaczeniem wpisu z bloga Roberta Cecila Wujka Boba Martina stąd:
Proszę o komentarze, gdyby ta luźność była zbyt daleko posunięta.

Testy - obywatele pierwszej kategorii

Mam wrażenie, że to może być jakieś fatum, że trafiam na blogi pisane przez ludzi, którzy poddali się negatywnemu działaniu nieszczęsnych dyscyplin, które z kolei doprowadziły ich do całkowitego zaprzestania pisania testów jednostkowych.
Ten blog jest przykładem jednego z takich.


Poniższy tekst jest luźnym tłumaczeniem wpisu z bloga Roberta Cecila Wujka Boba Martina stąd:
Proszę o komentarze, gdyby ta luźność była zbyt daleko posunięta.


Autor opowiada o tym, jak jego testy są wrażliwe, ponieważ mockuje wszystkie współpracujące odwołania zewnętrzne. (ech). Każdorazowo, gdy odwołania zewnętrzne się zmienią trzeba zmieniać mocki. I to oczywiście czyni testy jednostkowe wrażliwymi.

Autor opowiada nam, jak porzucił testowanie jednostkowe i zaczął robić coś, co nazywa “Testowaniem Systemowym”. W jego słowniku, “Testy Systemowe” były po prostu testami, które były bardziej end-to-end niż “testy jednostkowe”.

A więc najpierw trochę definicji. Wybacz mi moją nieposkromioną pychę, ale jest teraz tyle różnych definicji “testów jednostkowych”, “testów systemowych” czy “testów akceptacyjnych”, że ktoś powinien podać jedną autorytarną definicję. Nie wiem, czy te definicje się przyjmą; ale mam szczerą nadzieję, że jakimś definicjom uda się to przyszłości.

  • Test Jednostkowy: Test napisany przez programistę, aby upewnić się, że kod produkcyjny robi to, co powinien. (Na ten moment pomińmy fakt, że testy jednostkowe także ulepszają projekt aplikacji, itd.)
  • Test Akceptacyjny: Test napisany przez “biznes”, aby upewnić się, że produkcyjny kod robi to, co powinien. Autorami tych testów są ludzie od strony biznesowej, albo ludzie od strony technicznej, którzy reprezentują biznes, np. Analitycy Biznesowi i Testerzy.
  • Test Integracji: Test napisany przez architektów i/lub liderów technologicznych, aby upewnić się, że podzespół składników systemu działa prawidłowo. To są testy kanalizacyjne. Nie są zestawem testów reguł biznesowych. Ich celem jest potwierdzić, że podzespół jest zintegrowany i skonfigurowany właściwie.
  • Test Systemowy: Test integracyjny napisany , aby upewnić się, że bebechy całego zintegrowanego systemu działają tak jak wcześniej zaplanowano. Są to testy kanalizacyjne. Nie są zestawem testów reguł biznesowych. Ich celem jest potwierdzić, że system jest zintegrowany i skonfigurowany właściwie.
  • Mikro-test: Termin ukuty przez Mike’a Hill’a (@GeePawHill). Test Jednostkowy napisany w bardzo małym kontekście. Celem tego testu jest sprawdzenie jednej funkcji albo małej grupy funkcji.
  • Test Funkcjonalny: Test Jednostkowy napisany dla szerszego kontekstu z odpowiednim zestawem mocków dla powolnych komponentów.

Biorąc pod uwagę te definicje autor wcześniej wspomnianego bloga zrezygnował z pisania źle napisanych mikro testów, na korzyść źle napisanych testów funkcjonalnych. Dlaczego źle napisanych? Ponieważ w obu przypadkach autor opisuje te testy jako połączone do rzeczy, do których nie powinny być połączone.
W przypadku mikro-testów używał za dużo mocków i mocno powiązał swoje testy z kodem produkcyjnym. To oczywiście powoduje, że testy są kruche.

W przypadku testów funkcjonalnych, autor opisuje je jako całą drogę od Interfejsu Użytkownika do Bazy Danych i odnosi się do faktu, że były powolne. Cieszy się kiedy jego testy czasami działają tylko 15 minut. 15 minut to o wiele za dużo, jak na szybki feedback, który testy jednostkowe powinny nam dostarczać. Praktycy TDD nie mają w zwyczaju czekać na system ciągłej integracji, tylko po to, żeby stwierdzić, że testy przechodzą.
Doświadczeni praktycy TDD rozumieją, że ani mikro-testy, ani testy funkcjonalne (ani nawet testy akceptacyjne) nie powinny być mocno połączone z implementacją systemu. Powinny (tak jak autor bloga zachęca nas) być rozważone, jako część systemu i “... traktowane jako obywatele pierwszej kategorii: [One] powinny być traktowane w taki sam sposób, jak kod produkcyjny”.

To, czego wydaje się, autor bloga nie dostrzega, to to, że obywatele pierwszej kategorii systemu nie powinni być ściśle połączeni. Ktoś, kto traktuje swoje testy jako obywateli pierwszej kategorii podejmie wiele starań, aby zapewnić, że te testy nie są mocno powiązane z kodem produkcyjnym.

Rozluźnianie powiązań mikro-testów i testów funkcjonalnych z kodem produkcyjnym nie jest szczególnie trudne. Rzeczywiście wymaga trochę zdolności w projektowaniu oprogramowania i trochę wiedzy na temat technik rozłączania. Zasadniczo spora dawka Projektowania Zorientowanego Obiektowo i odwrócenia zależności, razem z rozsądnym użyciem Wzorców Projektowych (jak Fasada czy Strategia) wystarczą, aby rozluźnić nawet najbardziej szkodliwy zestaw testów.

Niestety, zbyt dużej liczbie programistów wydaje się, że zasady dla testów jednostkowych są inne - że testy mogą być “śmieciowym kodem” napisanym przy użyciu dowolnego sposobu, wybranego ad hoc, jaki tylko ci programiści uznają za stosowny. Także zbyt wielka liczba programistów czyta książki na temat mockowania i kupili te książki w przekonaniu, że narzędzia do mockowania są nieodłączną i potrzebną częścią testowania jednostkowego. Nic nie może być dalej od prawdy.

Ja, dla przykładu, rzadko używam narzędzia do mockowania. Kiedy potrzebuję mocka (albo, raczej Dublera Testowego) piszę go sam. Nie jest trudno pisać takie dublery testowe. Moje IDE bardzo mi w tym pomaga. Co więcej, pisanie Dublera Testowego zachęca mnie do pisania testów jednostkowych bez użycia Dublerów Testowych, no chyba że to jest bardzo potrzebne. Zamiast używać Dublerów Testowych, odchodzę na chwilę od pisania mikro-testów i piszę testy, które są ciut bliższe testom funkcjonalnym. To także pomaga mi rozdzielić testy od wnętrzności kodu produkcyjnego.

Podsumowując, powodem tego, że ludzie poddali się z pisaniem testów jednostkowych jest zwykle to, że nie podążali za powyższą radą autora. Nie traktowali testów jako obywateli pierwszej kategorii. Nie traktowali testów w taki sposób, aby były one nieodłączną częścią systemu. Nie utrzymywali standardów dla tych testów jakich utrzymywali dla innych części systemu. Zamiast tego pozwolili testom się psuć, stać się mocno powiązanymi z kodem, stać się niezmienialnymi, kruchymi i powolnymi. I wtedy, w wielkiej frustracji, porzucili testy.

Morał: Zachowaj swoje testy czyste. Traktuj je jako obywateli pierwszej kategorii w Twoim systemie.


Koniec konkursu Daj Się Poznać 2017

Nadszedł 31 maja 2017, a więc koniec konkursu Daj Się Poznać 2017.
Jestem bardzo wdzięczny. Wdzięczny Społeczności konkursu i Maćkowi Aniserowiczowi. Społeczności za możliwość czytania fantastycznych wpisów. Za burzliwe dyskusje na Slacku o konkursie, programowaniu, czy polityce. Maćkowi za to, że zorganizował pierwszy i najlepszy konkurs dla bloggerów technologicznych w Polsce.

Wiele się nauczyłem przez ten czas od Was - uczestników DSP. Mój dev i blog skill poszybował dzięki Wam o kilka poziomów do góry. Życzę każdemu z Was, abyście prowadzili regularnie Wasze blogi przynajmniej do następnej edycji konkursu. Dzięki za wszystko. Do następnego.

Pozdrawiam
Michał Kuliński



Wzorzec Obserwator - otrzymywanie subskrybowanych informacji.

Po tym jak mogę dodawać obiekty do listy subskrybentów zabieram się za otrzymywanie wiadomości.

Poprzedni post tu.

Napisałem sobie krótki test w Ruby:
def test_receiving_information                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
    @client = Client.new                                                                                                                                                                                                                     
    @media.add_advertising("5 55 555")                                                                                                                                                                                                       
    @media.add_to_subscribers @client                                                                                                                                                                                                        
    #@media.broadcast                                                                                                                                                                                                                        
    #assert_not_empty @client.ads                                                                                                                                                                                                            
                                                                                                                                                                                                                                             
end




Na razie wykomentowałem dalsze akcji i puszczam rake:

coola@sv26 [/home/coola/dsp2017/xp-simulator]# rake                                                                                                                                                                                          
/usr/share/ruby-2.3.1/bin/ruby ./test/all_tests.rb                                                                                                                                                                                           
Loaded suite ./test/all_tests                                                                                                                                                                                                                
Started                                                                                                                                                                                                                                      
......E                                                                                                                                                                                                                                      
=============================================================================================================================================================================================================================================
Error: test_receiving_information(SubscriptionTests): RuntimeError: Argument should have publish method                                                                                                                                      
/home/coola/dsp2017/xp-simulator/src/Media.rb:17:in `add_to_subscribers'                                                                                                                                                                     
/home/coola/dsp2017/xp-simulator/test/subscription_tests.rb:47:in `test_receiving_information'                                                                                                                                               
     44:                                                                                                                                                                                                                                     
     45:     @client = Client.new                                                                                                                                                                                                            
     46:     @media.add_advertising("5 55 555")                                                                                                                                                                                              
  => 47:     @media.add_to_subscribers @client                                                                                                                                                                                               
     48:     #@media.broadcast                                                                                                                                                                                                               
     49:     #assert_not_empty @client.ads                                                                                                                                                                                                   
     50:                                                                                                                                                                                                                                     
=============================================================================================================================================================================================================================================
                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
Finished in 0.002118131 seconds.                                                                                                                                                                                                             
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
7 tests, 9 assertions, 0 failures, 1 errors, 0 pendings, 0 omissions, 0 notifications                                                                                                                                                        
85.7143% passed                                                                                                                                                                                                                              
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3304.80 tests/s, 4249.03 assertions/s                                                                                                                                                                                                        
rake aborted!                                                                                                                                                                                                                                
Command failed with status (1): [/usr/share/ruby-2.3.1/bin/ruby ./test/all_...]                                                                                                                                                              
/home/coola/dsp2017/xp-simulator/Rakefile:4:in `block in <top (required)>'                                                                                                                                                                   
/home/coola/.gems/2.3/gems/rake-12.0.0/exe/rake:27:in `<top (required)>'                                                                                                                                                                     
Tasks: TOP => default => test                                                                                                                                                                                                                
(See full trace by running task with --trace)                                   


Okazuje się, że mój klient nie "implementuje" metody publish, potrzebnej dla obiektu Media do publikacji.

Dopisuję dwie linijki:

class Client                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                             
  def publish                                                                                                                                                                                                                                
  end  
                                                                                                                                                                                                                                      
end 

Dla rake to wystarczy:

coola@sv26 [/home/coola/dsp2017/xp-simulator]# rake                                                                                                                                                                                          
/usr/share/ruby-2.3.1/bin/ruby ./test/all_tests.rb                                                                                                                                                                                           
Loaded suite ./test/all_tests                                                                                                                                                                                                                
Started                                                                                                                                                                                                                                      
.......                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                             
Finished in 0.00171854 seconds.                                                                                                                                                                                                              
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
7 tests, 9 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications                                                                                                                                                        
100% passed                                                                                                                                                                                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4073.22 tests/s, 5237.00 assertions/s               


Odkomentowuję następną linijkę:

def test_receiving_information                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
    @client = Client.new                                                                                                                                                                                                                     
    @media.add_advertising("5 55 555")                                                                                                                                                                                                       
    @media.add_to_subscribers @client                                                                                                                                                                                                        
    @media.broadcast                                                                                                                                                                                                                         
    #assert_not_empty @client.ads                                                                                                                                                                                                            
                                                                                                                                                                                                                                             
end 


Tutaj przede wszystkim nie ma metody broadcast i jej implementacji.

coola@sv26 [/home/coola/dsp2017/xp-simulator]# rake                                                                                                                                                                                          
/usr/share/ruby-2.3.1/bin/ruby ./test/all_tests.rb                                                                                                                                                                                           
Loaded suite ./test/all_tests                                                                                                                                                                                                                
Started                                                                                                                                                                                                                                      
......E                                                                                                                                                                                                                                      
=============================================================================================================================================================================================================================================
Error: test_receiving_information(SubscriptionTests): NoMethodError: undefined method `broadcast' for #<Media:0x007f3f412768c0>                                                                                                              
/home/coola/dsp2017/xp-simulator/test/subscription_tests.rb:48:in `test_receiving_information'                                                                                                                                               
     45:     @client = Client.new                                                                                                                                                                                                            
     46:     @media.add_advertising("5 55 555")                                                                                                                                                                                              
     47:     @media.add_to_subscribers @client                                                                                                                                                                                               
  => 48:     @media.broadcast                                                                                                                                                                                                                
     49:     #assert_not_empty @client.ads                                                                                                                                                                                                   
     50:                                                                                                                                                                                                                                     
     51:   end                                                                                                                                                                                                                               
=============================================================================================================================================================================================================================================
                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
Finished in 0.002872458 seconds.                                                                                                                                                                                                             
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
7 tests, 9 assertions, 0 failures, 1 errors, 0 pendings, 0 omissions, 0 notifications                                                                                                                                                        
85.7143% passed                                                                                                                                                                                                                              
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2436.94 tests/s, 3133.21 assertions/s                                                                                                                                                                                                        
rake aborted!                                                                                                                                                                                                                                
Command failed with status (1): [/usr/share/ruby-2.3.1/bin/ruby ./test/all_...]                                                                                                                                                              
/home/coola/dsp2017/xp-simulator/Rakefile:4:in `block in <top (required)>'                                                                                                                                                                   
/home/coola/.gems/2.3/gems/rake-12.0.0/exe/rake:27:in `<top (required)>'                                                                                                                                                                     
Tasks: TOP => default => test                                                                                                                                                                                                                
(See full trace by running task with --trace)          


Dodaję zmiany w klasie Media:

class Media                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                             
  attr_accessor :ads, :subscribers                                                                                                                                                                                                           
                                                                                                                                                                                                                                             
  def initialize                                                                                                                                                                                                                             
    @ads = Array.new                                                                                                                                                                                                                         
    @subscribers = Array.new                                                                                                                                                                                                                 
  end                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
  def add_advertising telephone_number                                                                                                                                                                                                       
    @ads.push telephone_number                                                                                                                                                                                                               
  end                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
  def add_to_subscribers client                                                                                                                                                                                                              
    if !client.respond_to? :publish                                                                                                                                                                                                          
      raise 'Argument should have publish method'                                                                                                                                                                                            
    end                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                             
    @subscribers.push client                                                                                                                                                                                                                 
                                                                                                                                                                                                                                             
  end                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
  def broadcast                                                                                                                                                                                                                              
    @subscribers.each { |subscriber| subscriber.publish @ads }                                                                                                                                                                               
  end                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
end 


Używam metody publish z atrybutem i rzeczywiście rake mówi, że w klasie Client nie ma atrybutu metody publish:

                                                                                                                                                                                         
Loaded suite ./test/all_tests                                                                                                                                                                                                                
Started                                                                                                                                                                                                                                      
......E                                                                                                                                                                                                                                      
=============================================================================================================================================================================================================================================
Error: test_receiving_information(SubscriptionTests): ArgumentError: wrong number of arguments (given 1, expected 0)                                                                                                                         
/home/coola/dsp2017/xp-simulator/src/Client.rb:3:in `publish'                                                                                                                                                                                
/home/coola/dsp2017/xp-simulator/src/Media.rb:25:in `block in broadcast'                                                                                                                                                                     
/home/coola/dsp2017/xp-simulator/src/Media.rb:25:in `each'                                                                                                                                                                                   
/home/coola/dsp2017/xp-simulator/src/Media.rb:25:in `broadcast'                                                                                                                                                                              
/home/coola/dsp2017/xp-simulator/test/subscription_tests.rb:48:in `test_receiving_information'                                                                                                                                               
     45:     @client = Client.new                                                                                                                                                                                                            
     46:     @media.add_advertising("5 55 555")                                                                                                                                                                                              
     47:     @media.add_to_subscribers @client                                                                                                                                                                                               
  => 48:     @media.broadcast                                                                                                                                                                                                                
     49:     #assert_not_empty @client.ads                                                                                                                                                                                                   
     50:                                                                                                                                                                                                                                     
     51:   end                                                                                                                                                                                                                               
=============================================================================================================================================================================================================================================
                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
Finished in 0.002341671 seconds.                                                                                                                                                                                                             
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
7 tests, 9 assertions, 0 failures, 1 errors, 0 pendings, 0 omissions, 0 notifications                                                                                                                                                        
85.7143% passed                                                                                                                                                                                                                              
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2989.32 tests/s, 3843.41 assertions/s                                                                                                                                                                                                        
rake aborted!                                                                                                                                                                                                                                
Command failed with status (1): [/usr/share/ruby-2.3.1/bin/ruby ./test/all_...]                                                                                                                                                              
/home/coola/dsp2017/xp-simulator/Rakefile:4:in `block in <top (required)>'                                                                                                                                                                   
/home/coola/.gems/2.3/gems/rake-12.0.0/exe/rake:27:in `<top (required)>'                                                                                                                                                                     
Tasks: TOP => default => test                                                                                                                                                                                                                
(See full trace by running task with --trace)           

Dopisuję argument ads do klasy Client i jest OK.

Teraz odkomentowuję ostatnią linijkę testu:

def test_receiving_information                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
    @client = Client.new                                                                                                                                                                                                                     
    @media.add_advertising("5 55 555")                                                                                                                                                                                                       
    @media.add_to_subscribers @client                                                                                                                                                                                                        
    @media.broadcast                                                                                                                                                                                                                         
    assert_not_empty @client.ads                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
  end  

Rake krzyczy, że nie ma zmiennej klasowej ads i jest ona pusta:

Started                                                                                                                                                                                                                                      
......E                                                                                                                                                                                                                                      
=============================================================================================================================================================================================================================================
Error: test_receiving_information(SubscriptionTests): NoMethodError: undefined method `ads' for #<Client:0x007f1a96ef2420>                                                                                                                   
/home/coola/dsp2017/xp-simulator/test/subscription_tests.rb:49:in `test_receiving_information'                                                                                                                                               
     46:     @media.add_advertising("5 55 555")                                                                                                                                                                                              
     47:     @media.add_to_subscribers @client                                                                                                                                                                                               
     48:     @media.broadcast                                                                                                                                                                                                                
  => 49:     assert_not_empty @client.ads                                                                                                                                                                                                    
     50:                                                                                                                                                                                                                                     
     51:   end                                                                                                                                                                                                                               
     52:                                                                                                                                                                                                                                     
=============================================================================================================================================================================================================================================
                         

Dodaję zmiany:

class Client                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                             
  def publish ads                                                                                                                                                                                                                            
                                                                                                                                                                                                                                             
    @ads = ads                                                                                                                                                                                                                               
                                                                                                                                                                                                                                             
  end                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
  def ads                                                                                                                                                                                                                                    
    @ads                                                                                                                                                                                                                                     
  end                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
end

I teraz projekt przechodzi wszystkie testy :)


coola@sv26 [/home/coola/dsp2017/xp-simulator]# rake                                                                                                                                                                                          
/usr/share/ruby-2.3.1/bin/ruby ./test/all_tests.rb                                                                                                                                                                                           
Loaded suite ./test/all_tests                                                                                                                                                                                                                
Started                                                                                                                                                                                                                                      
.......                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                             
Finished in 0.001713314 seconds.                                                                                                                                                                                                             
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
7 tests, 10 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications                                                                                                                                                       
100% passed                                                                                                                                                                                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4085.65 tests/s, 5836.64 assertions/s 

Commit do tego tu.

To jest ostatni projektowy wpis w konkursie Daj się Poznać 2017. Dzięki Maćku i Społeczności :)

Wstęp do wzorca Obserwator, wyjątki i kacze typowanie w Ruby

Tymczasem w projekcie... Na razie klient może sobie dodać swój numer telefonu do obiektu mediów. Chciałbym teraz, żeby każdy z klientów mógł zasubskrybować medium i w dalszej perspektywie medium mogło informować klientów o egzemplarzach swoich periodyków. Jak zawsze, gdy mamy fazę green procesu TDD piszemy test jednostkowy:

def test_if_media_have_subscibers_structure
 
    assert_not_nil @media.subscribers                                                                    
                                                                                                          
end

Od razu jestem na czerwono:

...E                                                                                                      
==========================================================================================================
Error: test_if_media_have_subscibers_structure(SubscriptionTests): NoMethodError: undefined method `subscr
ibers'

Wchodzę w fazę red procesu TDD i piszę kod produkcyjny. I tak w kółko. Test Driven Development jest dyscypliną. Nie musi być mądre to, co robię, ważne żeby przyniosło oczekiwany efekt. Porównując to do koksika ćwiczącego na siłowni - podnoszenie 1000 razy tego samego ciężaru, jako takie, jest głupie, nic nie wnoszące. Ale z punktu widzenia rozwoju mięśni - takie ciągłe, zdawałoby się nieefektywne działanie, w dłuższej perspektywie prowadzi do rozwoju bica i trica.

No więc, żeby ten test przechodził - napisałem następujący kod produkcyjny:

class Media                                                                                               
                                                                                                          
  attr_accessor :ads, :subscribers                                                                   
                                                                                                          
  def initialize                                                                                          
    @ads = Array.new                                                                                      
    @subscribers = Array.new                                                                       
  end                                                                                                     
                                                                                                          
  def add_advertising telephone_number                                                                    
    @ads.push telephone_number                                                                            
  end                                                                                                     
                                                                                                          
end 

Szybkie uruchomienie rake wskazuje, że jestem w fazie green.

Started                                                                                                   
....                                                                                                      
                                                                                                          
Finished in 0.000670749 seconds.                                                                          
4 tests, 8 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications---------------------
                                                                                                          
100% passed

Zielono, więc piszę test, który sprawdza czy mogę dodać aktualnego klienta do listy subskrybentów w klasie mediów.


def test_if_client_can_be_added_to_subscribers_list                                                      
                                                                                                          
    it_people = ItPeople.new                                                                              
                                                                                                          
    @media.add_to_subscibers it_people                                                                    
                                                                                                          
  end 

Czerwono i do kodu produkcyjnego:

def add_to_subscribers client                                                                           
    @subscribers.push client                                                                              
end

Dooobra i zielono, ale chciałbym mieć w tej tablicy subscribers tylko takie obiekty, które implementują interfejs Publicable zawierający metodę Publish.

A więc najpierw test. Gdy do metody add_to_subscribers wpadnie coś, co nie implementuje Publicable poproszę o wyjątek:

def test_if_there_an_exception_if_there_was_no_publicable_implementation_on_entry                       
                                                                                                          
    assert_raises do                                                                                      
      @media.add_to_subscribers 125                                                                       
    end                                                                                                   
                                                                                                          
  end

Blok assert_raises sprawdza, czy w danym bloku kodu - pomiędzy do i end poleciał wyjątek. Spodziewam się, że nie będzie można dodać liczby 125 do tej kolekcji.
I rzeczywiście poleciał wyjątek (inny), bo nie poleciał wyjątek (w kodzie produkcyjnym):

/usr/share/ruby-2.3.1/bin/ruby ./test/all_tests.rb
Loaded suite ./test/all_tests
Started
.....F
==========================================================================================================
Failure: <[]> exception expected but none was thrown.
test_if_there_an_exception_if_there_was_no_publicable_implementation_on_entry(SubscriptionTests)
/home/coola/dsp2017/xp-simulator/test/subscription_tests.rb:37:in `test_if_there_an_exception_if_there_was
_no_publicable_implementation_on_entry'
     34: 
     35:   def test_if_there_an_exception_if_there_was_no_publicable_implementation_on_entry
     36: 
  => 37:                assert_raises do
     38:       @media.add_to_subscribers 125
     39:                end
     40:        end
==========================================================================================================
    

Jestem w fazie czerwonej, więc zawijam kiecę i lecę do kodu produkcyjnego:

def add_to_subscribers client                                                                           
  if client.instance_of? Publicable                                                                     
      @subscribers.push client                                                                            
  end                                                                                              
end

Metoda .instance_of? {nazwa_klasy} sprawdza, czy dany obiekt jest danego typu. Ok, nadal mam error - tym razem kompilacji, bo nie ma takiego bytu jak Publicable. Tworzymy odpowiedni interfejs i importujemy go.

Ale zaraz zaraz Ruby podobno nie ma w sobie interfejsów!

Jak to?

Okazuje się, że rzeczywiście nie ma interfejsów w Ruby, ponieważ Ruby jest językiem dynamicznie typowanym. W Ruby rozpoznajemy typ obiektu nie na podstawie deklaracji typu, ale poprzez badanie metod na tym obiekcie. To tak zwane kacze typowanie. Nazwa ta pochodzi od powiedzenia, że "Jeżeli coś chodzi jak kaczka i kwacze jak kaczka, to musi to być kaczką". Hmmm, ciekawe.



Ok, czyli moje sprawdzenie czy client jest obiektem pewnej klasy, zamieniam na sprawdzenie czy client ma konkretne metody. Dzieje się to przy użyciu:

.respond_to? :nazwa_metody

def add_to_subscribers client                                                                           
    if !client.respond_to? :publish                                                                       
      raise 'Argument should have publish method'                                       
    end                                                                                                   
                                                                                                          
    @subscribers.push client                                                                              
                                                                                                          
  end 

Czyli jeśli nie posiadasz metody publish to rzuć wyjątkiem.
Odpalam testy i okazuje się, że poprzedni test przestał działać:

Started                                                                                                   
..E                                                                                                       
==========================================================================================================
Error: test_if_client_can_be_added_to_subscribers_list(SubscriptionTests): RuntimeError: Argument should h
ave publish method                                                                                        
/home/coola/dsp2017/xp-simulator/src/Media.rb:17:in `add_to_subscribers'                                  
/home/coola/dsp2017/xp-simulator/test/subscription_tests.rb:31:in `test_if_client_can_be_added_to_subscrib
ers_list'                                                                                                 
     28:                                                                                                  
     29:   def test_if_client_can_be_added_to_subscribers_list                                            
     30:                                                                                                  
  => 31:                @media.add_to_subscribers @it_people                                              
     32:                                                                                                  
     33:        end                                                                                       
     34:     

A to dlatego, że rzeczywiście klasa ItPeople nie ma metody publish. Jeszcze nie ma ;)


class ItPeople                                                                                            
                                                                                                          
  attr_accessor :telephone_number                                                                         
                                                                                                          
  def publish                                                                                             
  end                                                                                                     
                                                                                                          
end 

No i jestem na zielono. Jestem tym zaskoczony, że w języku dynamicznym tak mało mam do czynienia z konkretnymi typami. Wydaje mi się, że taki język jest wygodniejszy do testowania jednostkowego. A już, że wyjątek rzuciłem przy użyciu słówka kluczowego raise i opisowego stringa to jest bajka :)  Ciekawe czy dynamiczne typowanie doprowadzi mnie do jakiejś katastrofy, czy nadal będę mógł jechać na autopilocie testów jednostkowych, mając zawiązane oczy na konkretne typy.

Kommit do tego TU.