12. 7. 2016

Psaní čitelných testů: case study


V naší firemní kultuře v Topmonks máme takový zvyk: Každý týden dostane někdo pověření starat se o firemní sociální sítě. Tento týden to připadlo na mě. Vzhledem k tomu, že se sociálními sítěmi kromě Twitteru a LinkedInu si jinak netykám, připadal jsem si asi takto:


Ale mám rád výzvy a přemýšlel jsem, jak se takové výzvy zhostit. Slovy klasika z reklamy, mohli jsme vám nasdílet nějakou hypergeekovskou zábavu. Anebo fotku zátiší s notebookem z netradičních míst (zde střecha dílny na stavbě, nikde jinde v baráku totiž místo nebylo). Ale my jsme řekli ne! Možná to některé překvapí, ale v Topmonks taky kromě jiného pracujeme. Takže žádné 🍹 👙, ale do it hard way – rozhodl jsem se vyprázdnit buffer a spíchnout dohromady pár nedávných zkušeností. A když už je tedy to léto, tak to odlehčíme, tj. nebude to o produkčním kódu, ale jen o psaní testů Gemoji image for :trollface:.

Úkol, který jsem řešil, byl naimportovat z příchozího JSONu data do databáze, přesněji do 8 entitních typů vzájemně propojených všemi typy vztahů (many-to-many, one-to-many, many-to-one). Synchronizace dat mezi různými prostředími je důležitá operace a importní tool je pro ni stěžejní, proto jsme algoritmus nejen probírali na týmových mítincích, ale přirozeně ho chceme mít pokrytý vypovídajícími testy.

Před refactoringem 


Prohlédněme si následující zjednodušenou a minimalizovanou ukázku. Vychází z reálné situace, kde v uvedené formě plně vyhovovala, dokud nebylo potřeba dělat větší zásahy do testovaného kódu.

Pro lepší kontext ještě pár technikálií: doménový model je reprezentován POJO objekty bez použití ORM frameworku (POJO neuchovávají asociace). Všechny databázové operace jdou pouze přes JDBC. ID objektů jsou typu String a fakticky obsahují UUID, které automaticky přiřadí DAO vrstva při vytvoření záznamu. Objekty mají kromě ID i unikátní klíč z business dat (název). DTO objekty reprezentující vstup importu obsahují – na rozdíl od POJO – i asociace. Testy jsou psané v JUnitu.

public class Cat {
    String id;
    String name;
    Breed breed;
}

public class CatDto extends Cat {
    List<Kitten> kittens;
}

public class Kitten {
    String id;
    String catId;
    String name;
    Color color;
}

public enum Breed {
    BRITISH_SHORTHAIR,
    MAINE_COON,
    DOMESTIC_CAT;
}

public class CatImportServiceTest {

    @Inject
    private CatImportService catImportService;

    @Inject
    private CatDao catDao;

    private Cat existingCatWithThreeKittens;

    private CatDto receivedCat;
   
    @Before
    public void setup() {
        jdbcTemplate.update("delete from kittens");
        jdbcTemplate.update("delete from cat");
        Cat c1 = new Cat();
        c1.setName("Felix");
        c1.setBreed(Breed.DOMESTIC_CAT);
        existingCatWithThreeKittens = catDao.create(c1);
        List<Kitten> kittens = new ArrayList<>();
        Kitten kitten1 = new Kitten();
        kitten1.setId("10");
        kitten1.setCatId(existingCatWithThreeKittens.getId());
        kitten1.setName("Tiger");
        kitten1.setColor(Color.GRAY);
        kittens.add(kitten1);
        Kitten kitten2 = new Kitten();
        kitten2.setId("20");
        kitten2.setCatId(existingCatWithThreeKittens.getId());
        kitten2.setName("n1");
        kitten2.setColor(Color.BLACK);
        kittens.add(kitten2);
        Kitten kitten3 = new Kitten();
        kitten3.setId("30");
        kitten3.setCatId(existingCatWithThreeKittens.getId());
        kitten3.setName("dummy");
        kitten3.setColor(Color.WHITE);
        kittens.add(kitten3);
        catDao.createKittens(existingCatWithThreeKittens.getId(), kittens);
    }

    @Test
    public void importNewCatCreatesTheCat() {
        receivedCat = new CatDto();
        receivedCat.setId("2");
        receivedCat.setName("Charlie");
        final Breed expectedBreed = Breed.MAINE_COON;
        receivedCat.setBreed(expectedBreed);
        List<Cat> receivedCats = new ArrayList<>();
        receivedCats.add(receivedCat);
        doImport(receivedCats);
        Cat importedCat = catDao.getByName("Charlie");
        assertEquals(expectedBreed, importedCat.getBreed())
        assertEquals(2, catDao.getAll().size())
    }

    @Test
    public void importExistingCatReplacesHerKittens() {
        receivedCat = new CatDto();
        String receivedCatId = "2";
        receivedCat.setId(receivedCatId);
        receivedCat.setName("Felix");
        receivedCat.setBreed(Breed.MAINE_COON);
        List<Kitten> kittens = new ArrayList<>();
        Kitten kitten1 = new Kitten();
        kitten1.setId("30");
        kitten1.setCatId(existingCatWithThreeKittens.getId());
        kitten1.setName("some");
        kitten1.setColor(Color.BROWN);
        kittens.add(kitten1);
        Kitten kitten2 = new Kitten();
        kitten2.setId("40");
        kitten2.setCatId(existingCatWithThreeKittens.getId());
        kitten2.setName("any");
        kitten2.setColor(Color.YELLOW);
        kittens.add(kitten2);
        receivedCat.setKittens(kittens);
        List<Cat> receivedCats = new ArrayList<>();
        receivedCats.add(receivedCat);
        doImport(receivedCats);
        Cat catInDb = catDao.getByName("Felix");
        List<Kitten> kittensInDb = catDao.getKittens(catInDb);
        assertEquals(3, kittensInDb.size())
        assertEquals("n1", kittens.get(0).getName());
        assertEquals(Color.BLACK, kittens.get(0).getColor());
        assertEquals("some", kittens.get(1).getName());
        assertEquals(Color.BROWN, kittens.get(1).getColor());
        assertEquals("any", kittens.get(2).getName());
        assertEquals(Color.YELLOW, kittens.get(2).getColor());
    }

    void doImport(List<CatDto> receivedCats) {
        catImportService.importAllCats(receivedCats);
    }
   
    void doImport(CatDto receivedCat) {
        catImportService.importAllCats(Collections.singletonList(receivedCat));
    }
   
}

Které vlastnosti takového testu Vám nejvíc vadí? Tady je můj seznam:

Refactorujeme

Společný setup

Možná nejkontroverznější téma hned na začátek.

S masivním rozšířením JUnitu jsme si zvykli, že testovací třída má řadu @Test metod a pak zpravidla jednu setupovací @Before metodu (pozn.: těch může samozřejmě technicky být i víc, ale neznamená to různý setup pro různé testy a není mezi nimi ani specifikováno pořadí). Aby bylo jasno, samotnou testovací pattern Arrange-Act-Assert považuji za dobrou. Mám ale dojem, že někdy máme sklon dávat do @Before části věci, které patří do části @Test. Podpořme to ještě důvěryhodným argumentem o DRY, proti němuž nikdo nic nenamítne, a vznikne obvykle třída, kde se v @Before připraví univerzální setup, na který pak začnou z různých směrů útočit testovací metody.

Problém tohoto přístupu je, že není vhodný pro úlohy typu import. Logika importu je funkcí nejen vstupních dat, ale i aktuálního setupu databáze. Setup databáze je tedy přirozenou součástí @Test metod, do @Before patří skutečně pouze základní společné věci jako smazání všech tabulek nebo autorizace. Domnívám se, že v tomto případě není univerzální setup vhodný vzhledem k rozmanitosti testovacích dat – buď musí testovací metoda ještě něco přidat a setup se tříští na dvě místa, nebo odmazat, případně nevyužít, a setup je tak nepřehledný. (Např. kočka, která se v ukázce nasetupuje, je pro test importNewCatCreatesTheCat nejen zbytečná, ale dokonce ho i zamlžuje assertem na celkovou velikost, který s existencí předpřipravené kočky musí počítat.)

Jak z toho ven?

Pokud se setupy pro jednotlivé testy dají seskupit do malého počtu typových případů, pak je lze rozdělit do několika testovacích tříd (případně, pokud jeden setup navazuje na jiný, tuto návaznost vyjádřit dědičností tříd s provoláním super.setup()). Osobně se ale domnívám, že cena za takové zavedení principu DRY je ale vyšší než se zdá a vzhledem k zesložitění v oblasti počtu tříd a hierarchie se to nevyplatí.

Řešením je změnit přístup. Setup přes @Before je push-přístup typický pro framework, kdy přizpůsobujeme návrh vlastního kódu hookům, které navrhl autor frameworku. Zaměřme se raději na to, aby každá testovací metoda si nasetupovala jen to, co opravdu potřebuje (pull-přístup), a jak to udělat co nejsrozumitelněji. O tom budou i zbylé tipy.

Tvorba testovacích objektů

JavaBeans specifikace vznikla v roce 1997, kdy si snad nikdo nepředstavoval Javu jinak než jako imperativní jazyk. Klasická tvorba POJO objektu pomocí série z příkazu new a několika setterů sice z pohledu různých builderů a fluent zápisů působí zastarale (a v některých ohledech oprávněně – např. nutí k samostatné deklaraci a vymýšlení jména pro objekt jen proto, že je potřeba ho nasetovat), má však svoje místo i dnes. Není to pouze díky své jednoduchosti, ale v prostředí testů zejména i díky tomu, že je možno si vybrat, které properties nastavíme. Pro maximální jednoduchost a čitelnost testu by mělo být zásadní, aby se nastavovaly jen ty properties, které hrají roli v otestování dané funkcionality.

Pokud klasický způsob tvorby POJO objektu překáží (např. pro nepoužitelnost ve fluent zápisu) a sada nastavovaných properties je jedna a táž, pak preferuji použití factory metody (buď v testu nebo přímo v POJO třídě). Pokud je sad víc, ale malý únosný počet, pak se ještě dá spokojit s přetížením factory metod. Tady ovšem pozor, aby se přetěžování nezvrhlo co do množství přetížení nebo dodržení best practices. Ve zcela obecném případě, tj. chceme-li fluent zápis a přitom flexibilní volbu nastavovaných properties, pak zbývá jen builder pattern.

Osobně používám fluent zápis rád, považuji proto jak přetěžované metody, tak builder pattern za dobrou pomůcku. Osvědčila se konvence začínat factory metody prefixem new... (na rozdíl od názvosloví create..., které je vyhrazeno pouze pro tvorbu v databázi na úrovni DAO), což pak umožní one-linery na způsob create(newFoo()) s čitelností na úrovni anglické věty.

Tvorba testovacích kolekcí

Podobně jako POJO objekty, i kolekce můžeme tvořit klasicky i pomocí jednořádkového zápisu. Na rozdíl od POJO objektů je zde ale lepší podpora, ať už pomocí vestavěného Arrays.asList() či s podporou Guava metody ImmutableList.of().

Pokud se testuje funkcionalita, která má na vstupu kolekci, jistě by nemělo zůstat u toho, aby veškeré testy pracovaly pouze s jednoprvkovou kolekcí. Nicméně pro některé testy kolekce o jediném prvku stačí a pak je zbytečné testy komplikovat. V tom případě se nabízí přetížit příslušné metody, aby akceptovaly jak kolekci, tak jediný prvek. Já to ale považuji za past a zbytečný syntaktický cukr, který pouze snižuje ortogonalitu (rada #40 v Effective Java).

V uvedené ukázce tedy preferuji použít jen metodu doImport(List). Volání s jediným prvkem nechť explicitně uvedou Collections.singletonList() a bude to každému jasné. Kromě toho takto redukovaná signatura mnohem lépe škáluje, protože při n různých Listech na vstupu stačí pořád jediná metoda, zatímco při přetížení každého argumentu bychom potřebovali n! metod 2n metod.

Samovypovídající vstupní data

Dalším místem, kde lze zlepšit čitelnost testů doslova zadarmo, je volba testovacích dat. Pokud z hlediska testu dává smysl naplnit stringový field libovolným řetězcem, pak namísto náhodného nicneříkajícího řetězce preferuji řetězec, který přináší přidanou dokumentační hodnotu. Testuji přidání nové kočky? Raději než hledat v kalendáři kočičích jmen, ji pojmenuju Newcat. Testuji logiku zaměřenou na situaci, kdy osoba má stejné jméno a příjmení? Raději než Franta Franta ji pojmenuju Same Same. Testuji, zda seznam řetězců neobsahuje hodnotu? Nastražím data testu tak, abych mohl napsat assertFalse(list.contains("nonexisting")). Testuju CRUD operace nad koťaty? Pojmenuju si příslušná koťata Created, Deleted, Changed, Unchanged. A tak dále.

I u číselných typů se lze přiblížit podobnému samodokumentačnímu efektu, pokud si zvolená čísla s sebou nesou nějakou známou (ať pozitivní či negativní) konotaci. U typů s omezenou doménou jako např. enumy nebo boolean uvedený postup použít nelze, nicméně i tam je lepší zvolit hodnoty tak, aby celkově test vypadal konzistentně.

A když už naše testovací data tak pěkně vypadají a je z nich patrné, že to nejsou náhodně vymyšlené hodnoty, pak by bylo kontraproduktivní to vysušovat a ukládat tyto hodnoty do lokálních proměnných. Ano, z hlediska formální správnosti není nic přesnějšího než v metodě importNewCatCreatesTheCat uložit očekávané plemeno kočky do finální proměnné a proti ní pak porovnávat skutečnou hodnotu. Jenže to stopování výskytů proměnné při čtení testu už mi přijde stejně namáhavé jako stopování výskytů konstanty, že mi to za ten řádek navíc a vymýšlení názvu nestojí.

Vlastní asserty

Podobně jako si můžeme zčitelnit tvorbu objektů, můžeme zčitelnit asserty. Není nutné se po řádkách ptát přes assertEquals na jednotlivé fieldy: máme JUnit Matchery nebo můžeme vytknout opakující se věci do šikovně pojmenované metody. Obzvlášť ve spojení s lambda funkcemi se dá dosáhnout poměrně srozumitelného zápisu na malém počtu řádků.

Trochu offtopic: v uvedené ukázce se test na koťata v databázi spoléhá na seřazení podle ID. To dělá test fragilním (a pokud je řazení myšleno jako součást testu, pak by měla existovat vlastní testovací metoda pouze na něj). Vylepšené implementaci pomocí predikátu už na pořadí nezáleží, stále však netestuje, že predikát našel pokaždé jiný prvek. To se pojistí teprve až testem na velikost množiny, do které se objekty nahází (za předpokladu, že mají na ID postavenou metodu equals).

Ostatní, menší smells

Je to asi věc vkusu, ale používám statické importy, kde můžu, obzvlášť u enum konstant a factory metod pro kolekce jako emptyList() a singletonList() (pozn.: u emptyList() toto před verzí Javy 8 nebylo možné vzhledem ke slabé typové inferenci a nutnosti použít explicitní zápis Collections.<Foo>emptyList()).

Setup na testovacích metodách by neměl zacházet do nižší úrovně abstrakce, tj. na úroveň SQL, ale měl by zůstat na úrovni DAO.

Po refactoringu


public class CatImportServiceTest {

    @Inject
    private CatImportService catImportService;
 
    @Inject
    private CatDao catDao;
 
    @Before
    public void setup() {
        catDao.deleteAllKittens();
        catDao.deleteAllCats();
    }
 
    @Test
    public void importNewCatCreatesTheCat() {
        CatDto receivedCat = newCat("1","Newcat",MAINE_COON);
        catImportService.importAllCats(singletonList(receivedCat));
        Cat importedCat = catDao.getByName("Newcat");
        assertEquals(MAINE_COON, importedCat.getBreed())
        assertEquals(1, catDao.getAll().size())
    }
 
    @Test
    public void importExistingCatReplacesHerKittens() {
        catDao.create(newCat("1","Existing",DOMESTIC_CAT));
        catDao.createKittens("1", ImmutableList.of(
                newKitten("10","1","Deleted",RED),
                newKitten("20","1","Unchanged",WHITE),
                newKitten("30","1","Changed",BLACK)
        ));
        CatDto receivedCat = newCat("1","Existing",DOMESTIC_CAT)
                .withKittens(ImmutableList.of(
                        newKitten("20","1","Unchanged",WHITE),
                        newKitten("30","1","Changed",BROWN),
                        newKitten("40","1","Created",GREEN)
                ));
        catImportService.importAllCats(singletonList(receivedCat));
        Cat catInDb = catDao.getByName("Existing");
        List<Kitten> kittensInDb = catDao.getKittens(catInDb.getId());
        assertEquals(3, ImmutableSet.copyOf(kittensInDb).size());
        assertKittenExists(kittensInDb, k -> "Unchanged".equals(k.getName()) && WHITE == k.getColor());
        assertKittenExists(kittensInDb, k -> "Changed".equals(k.getName()) && BROWN == k.getColor());
        assertKittenExists(kittensInDb, k -> "Created".equals(k.getName()) && BLACK == k.getColor());
    }

    protected static void assertKittenExists(List<Kitten> actualKittens, Predicate<Kitten> condition) {
        assertTrue(actualKittens.stream().filter(predicate).count() == 1);
    }
   
}


Shrnutí

Nechávám na čtenáři posouzení, zda popisované refactoringy čitelnost kódu vylepšily. Docela dost je možné, že pohled na to, co je čitelný kód, má každý trochu jinde a také kvůli této validaci jsem blogpost psal. Nicméně chtěl jsem demonstrovat, že ne všechny poučky, které bychom se neodvážili porušit v produkčním kódu, musí nutně pomáhat i v prostředí testů. Svoji roli tu hrají i vlastnosti Javy jako silné typování a některé slabiny syntaxe.

A jestli se Vám článek líbil, vytrollil, popřípadě nechal jiné silné emoce, lajkujte, retweetujte, přijďte k nám nezávazně na pokec na snídani nebo rovnou rozšiřte naše řady :-). Hezké léto!








7. 6. 2016

Wisephora 2016

Ve čtvrtek 26.5.2016 se v prostorách Jatek78 v pražských Holešovicích uskutečnila jednodenní konference Wisephora na téma budoucnosti trhu práce a s tím souvisejících témat. Od rána do večera běžel pestrý a nabitý program, jehož ústřední částí bylo 20 lightning talků nejen na HR-témata jako např. hledání zaměstnanců, práce na dálku, komunikační a organizační nástroje apod., ale mnohdy přesahujících i do dalších oblastí lidského života. Tweetovat jsem stíhal jen omezeně, ale rád bych se podělil o asi 11 stránek A5 poznámek, odkazů, tipů a myšlenek, které jsem si na konferenci pořídil,

Na začátku osobní poznámka: Pracuji v pořadatelské společnosti TopMonks (i když jsem nepatřil do pořadatelského týmu) a i přes tento "bias" musím konferenci upřímně a hlasitě pochválit! Užil jsem si ji po obsahové stránce, většina řečníků byla velmi inspirativní a myslím, že sdílení, kvůli kterému konference vznikla, tam pěkně proudilo. A analogicky, protože jednou z našich company values je i "Provide honest feedback", otevřeně sem píšu i co se mi nelíbilo nebo co mne neoslovilo, takže určitě není na místě obava, že jsem dostal za úkol napsat PR článek na míru. Pro větší schematičnost budu navíc talky hvězdičkovat.

Výpisky z jednotlivých talků

Daniel Steigerwald – Připravme se na vetokracii, sametový rozpad států

  • intuitivní != nové. Nové věci nebývají intuitivní, to by tu jinak už dávno byly.
  • nové věci bývá nesnadné prosadit, po zaběhnutí se ale divíme, jak mohla tak samozřejmá věc narážet na takový odpor (ilustrováno na případu Ignáce Filipa Semmelweise – maďarského lékaře, který prosazoval dezinfekci vápenným roztokem na porodních sálech a přestože docílil snížení úmrtnosti rodiček, nevěřili mu, že je to tím)
  • pokud nevíte, do čeho se pustit s podnikáním, vyberte si oblast, ve které selhává stát
  • mnoho pěkných myšlenek týkajících se "überizace" (mobilní aplikace pro hlášení policii apod.)
  • souhlasil jsem i s kritikou přehnaného zasahování státu a umělé regulace
  • nesouhlasím ovšem s libertariánskou myšlenkou rozpadu státu ve prospěch zajišťování veškerých funkcí soukromými subjekty – tento přístup považuji za utopistický a v prezentované podobě technokraticky odlidštěný.
I když mne na talku řada myšlenek oslovila, celkově mi to přišlo jako na předvolebním shromáždění strany Svobodných. Nemám dojem, že odpovědí na babišooligarchický způsob správy státu je stát zrušit, software, které požírá svět a požere i stát, není všespásné. 🌟

Vojta Roček – Áčkoví hráči

  • predikce: do 10 let nebude mít 1/2 lidí práci, za dalších 20 let ji ztratí 1/2 programátorů
  • s nástupem počítačů nastal stav "málo lidí optimalizuje mnoho"
  • a pokračuje do "málo lidí optimalizuje všechno"
  • desítky tisíc jobů nahradily algoritmy
  • algoritmy seřežou vše na stejnou úroveň, nezbyde prostor pro invenci střední vrstvy
  • Áčkoví hráči, vlastnosti, jak je najít a udržet
  • grow mindset x fixed mindset
  • experiment: 2 skupiny dětí dostaly složit puzzle. Ty, které byly chváleny za proces, si vybraly příště těžší, ty, které byly chváleny za výsledek, si vybraly příště lehčí.
  • feedback
  • kontroluje emoce
  • nezáleží tolik na inteligenci a dovednostech
  • delivery – bez toho je to jen žvanil
  • purpose – lidi chtějí dělat na něčem, co má smysl a mají dojem, že to zlepšuje svět
  • "co budou dělat béčkoví hráči?" – "uhnijou v křesle s brýlemi na virtuální realitu – když si nedovedou najít smysl, budou se muset někam uklidit"
Shrnuto: pěkně popsaný "profil" člověka s otevřenou myslí. Prognóza týkající se lidí, kteří se takovou charakteristikou nevyznačují, mi však vyzněla otevřeně a do ztracena. 🌟🌟

Tomáš Formánek – Supply Chain Love – jedno srdce firmy

  • doporučené video: rozhovor René Redzepiho a Jiro Ono (oba majitelé špičkových restaurací, druhý z nich je 90letý Japonec, který denně obsluhuje své hosty a servíruje jim sushi)
  • všechny stroje a algoritmy, které stavíme, dělají svět komplikovanějším, ne jednodušším
  • komplikovanost ilustroval na věcech z běžného života: zpráva od lékaře, smlouva s bankou, dloubnul si i do rozdílu v délce konstituce Spojených států a odpovídajících dokumentů Evropské unie
  • paradox: i samotná kniha One thing, která je zhruba řečeno o metodě Getting Things Done modifikované na seznam úkolů o jediné položce, má 276 stran
  • Bullwhip effect – jev z problematiky správy dodavatelského řetězce (supply chain), kdy vlivem velkého počtu článků řetězce může poptávka malého rozsahu na konci vynutit výrobu popř. skladování v mnohem větším rozsahu v průběhu řetězce.
I když mluvil hodně o konkrétním produktu své společnosti, získal si mě svým důrazem na jednoduchost. A možná trochu i tím, že byl první, který se v projevu obešel bez vulgárních výrazů. 🌟🌟🌟

Ondřej Štefl – Budoucnost začíná ve škole

  • zajímavé povídání o Scio školách, spousta faktů
  • konstruktivismus = stavění na tom, co děti už umí, alternativně ale taky i rozbíjení vžitých vzorců
  • po příchodu ze školy se zeptat dítěte: "co ti dnes ve škole udělalo radost?", spíš než "jakou jsi dostal známku?"
  • doporučené knihy: John Medina: Pravidla mozku a Pravidla mozku dítěte
  • klasickou školní třídu lze přirovnat co do jednostrannosti komunikace (učitel řídí, žáci poslouchají) k podobným situacím z jiných oborů: armáda, liturgie (nazváno nepřesně církev), továrna
  • Platón: "Když něco špatného sníme, lze to zvrátit. Když se nám do hlavy dostane něco špatného, už se toho nejde zbavit." – výborná paralela
  • důležitost dopadu učiva na reálný život. Byl prováděn experiment: po vysvětlení učiva o molekulách žákům 2. stupně jim byla položena otázka: "skládá se kyselina sírová z molekul?" (správně odpovědělo 97%) x "skládá se řízek nebo tvoje tělo z molekul?" (správně odpovědělo jen 30%)
  • dobrá škola děti naučí vypořádat se se změnou – "když se blíží bouře, někdo staví hradby, někdo větrné mlýny"
  • ve Scio škole namotivovali rodiče i k půjčení peněz škole – "když nám svěřili děti – to nejcennější – proč ne peníze?"
  • případ absurdní kreativity v plnění školských předpisů: zákon umožňuje spojit třídy, ale ne žáky z různých tříd, potřebovali spojit třídy – zjistili si, že zákonný požadavek by byl splněn, pokud by měli třídy o 1 žákovi. Nakonec se to neuskutečnilo, ale demostroval tím, jak je současná legislativa psaná na míru tradičním školám a alternativním nevyhovuje.
Toto byl celkově jeden z nejlepších talků a takovým školám fandím. Velmi mě potěšil i odpovědí na dotaz na využívání techniky k výuce – právě tím, že jednoznačnou odpověď nedal. Trochu jsem očekával lacinou chválu, jak jsou všechny ty tablety apod. potřeba, ale přednášející se vyjádřil zdrženlivě a i když pomocnou techniku samozřejmě používají, přesvědčil mne, že jeho vztah k ní není černobílý.
Nemám talku co vytknout, snad jen pro úplnost mám dojem, že mnoho dobrého z vlastností Scio škol lze dosáhnout i na klasické škole, pokud má ředitel a učitelé otevřenou mysl. Což <otevřenáreklama> je případ  ZŠ J.K.Tyla Písek</otevřenáreklama>, kam chodí moje děti :-). 🌟🌟🌟🌟🌟

Libor Malý – Cesta ke štěstí začíná vědomou laskavostí a štědrostí

  • začít u sebe – svět okolo bude hezký, když vy začnete první
Příběh jobs.cz – altruismu přetaveného do úspěšného businessu. Jinak poznámky nemám, přišel jsem na talk pozdě. 🌟🌟🌟

Tomáš Hruda – Velká vzdělávací fúze

  • bývalý náměstek MŠMT
  • educationrepublic.cz
  • doporučil dokument Enron (o vnější motivaci) 
  • "Není lepší cesta, jak se naučit někoho řídit, než být podřízen někomu, kdo to umí."
  • význam firemní kultury ("Dobrá firemní kultura má schopnost napravovat i nejhorší případy lidí, kteří do firmy přišli se zpočátku nějak pokřiveným pohledem.")
  • z akvária uděláte rybí polívku snadno, naopak už to jde těžko 🌟🌟🌟🌟

Jan Sechovec – Digitální pankáči

O týmu v České spořitelně, kde to trochu znám. Jasně, že digitální pankáči tam dělají něco, aby spořka nebyla korporace zatáhlá, ale tento pojem se vyskytoval v talku (jehož značnou část navíc tvořilo ilustrační video řekněme korporátních nešvarů) tak často, že už jsem to vnímal jako bzučení hmyzu. Včetně variací "my jsme to zpunkovali". Ale ta videa doporučuji zkouknout, jsou povedená. 🌟🌟

Tomáš Ervín Dombrovský – Věštění z křišťálové koule

  • obráběč = dříve ten, kdo ovládal soustruh, dnes ten, kdo ovládá roboty, kteří ovládají soustruh
  • bude totéž v budoucnu platit o kodérovi?
  • svoboda volby = zodpovědnost za to, co dělám
  • jeden z mála talků, který řešil otázku smyslu 🌟🌟🌟🌟

Petr Skondrojanis – Human2Human

  •  "V době tlaku na výkon a zisk nemůžeme očekávat, že etika a humanismus zvítězí nad ziskem." (pokud myšleno jako konstatování, pak jakžtakž ok, pokud myšleno jako výzva či rezignace, pak to není dobré)
  • Stroj bude tam, kde nechybí lidská laskavost a úsměv.
  • Přidaná hodnota = kde člověk nabídne víc než stroj.
  • Potřebujeme lidi, kteří umí nabídnout přidanou hodnotu.
  • příklad: sprchy+posilovna = benefity, aby zaměstnanci byli motivováni v práci být...
  • ..."a až budete mít první dítě, už to nepůjde, ale to už půjdete jinam" (rada "půjdete jinam" zní sama zvláštně, a navíc jak se při takovém stylu vůbec dostanu k tomu mít to první dítě?)
Celkově mám z tohoto talku ambivalentní a spíš nepříjemný dojem, jak je vidět i z poznámek. Spousta HR pouček a navzdory dvěma výskytům slova Human pocit absence lidskosti a udržitelného rozvoje. Pozitivní věci to nevyvážily. 🌟

Jiří Rozvařil – Co se bojíme říkat nahlas, aneb ryba smrdí od hlavy

  • "fabriky jsou plné lidí, zapomeňte na roboty, to jsou jen marketingové materiály Tesly"
  • "Sami si děláte, že vás ta práce nebaví a nenaplňuje a hledáte štěstí po konferencích."
  • iceberg of ignorance -> data potřebují ti dole, ne ti nahoře
  • vzorec Um = sum(Up) – úspěch managera je suma úspěchů podřízených
  • motivace není plošný postřik
  • otázka jak motivovat by měla být spíš otázkou jak přestat demotivovat – "Vy někoho serete celej den a pak si uděláte poradu o tom, jak ho motivovat."
  • doporučené knihy: Antonín Cekota: Tomáš Baťa a Jack Stack: The great game of business
Zemitě upřímný talk, přednášející hovořil jasně a na rovinu o věcech, které měly hlavu a patu. Také jsem ocenil pojetí leadershipu jako nástroje služby a zájmu o člověka. A sdílím s přednášejícím i respekt k Tomáši Baťovi. 🌟🌟🌟🌟🌟

Petr Pouchlý – Gamifikace pro novou generaci

  • 4 herní typy / archetypy přístupu ke hře: alea (víra, že porazím náhodu), ilinx (závrať, mimo mysl), mimicry (touha být jako někdo jiný), agon (soutěživost)
  • každý typ může mít zdravé i nezdravé prožívání (např. mimikry = divadlo x přetvářka)
  • paldia (= play, hrát si na...) x ludus (= game, hra s pravidly a systémem)
  • dopad her na praxi: efektivita výrobní linky x stavba věží příšerám, manažer fotbalové ligy
  • komentovaná definice gamifikace: využití herních prvků (lidi baví hrát) v neherním prostředí (firma) za účelem zvýšení loajality a angažovanosti
  • zaměstnance z generace Z namotivuje, když průchod firmou bude jako průchod hrou
Přednášející mluvil jasně, výrazně a systematicky. Toto téma jsem dřív nebral moc vážně a talk mě přiměl postoj přehodnotit. 🌟🌟🌟🌟

Jiří Fabián – Walking Zombies

  • Peter Drucker – Knowledge worker
  • nemusíte být vystresovaný, že teď musím tu svoji firmu rychle změnit, protože to všichni dělají
  • firmy ztratily reflexy, mají míň tykadel ven (např. pouze salesák)
  • mějte rádi chaos, mějte rádi změnu (elektronická tužka Roberta Záruby)
  • pull postoj = aktivně tahá změny x push postoj = čeká, až změny přijdou a někdo na mě hodí úkol
  • Švarc systém je pojmenován podle podnikatele Miroslava Švarce
  • organizační hierarchie ve smyslu klasických stromových diagramů je lež, realita vztahů je jiná
  • kmen se přeceňuje, firma je améba s neostrou hranicí
  • obrázek BOSS x LEADER je zavádějící, situaci s BOSSem samozřejmě nechceme, ale ani LEADER není jen jeden, nýbrž jsou různě dle zkušeností v konkrétní podoblasti
  • svobodná firma = za každé rozhodnutí musí být někdo zodpovědný
  • purpose –> engagement, competencies, results, respect
Zaujala mě zmínka o ústupu od funkcionálního dělení (tým inženýrů, tým testerů) – je až s podivem, že v organizační struktuře je každému jasné, jak to neškáluje, ale pokud jde o návrh architektury a rozdělení kódu, ještě pořád tolik programátorů preferuje antipattern package-by-layer. 🌟🌟🌟🌟🌟

Talky Michala Šrajera a Sváti Kotyzové a Blanky Laurychové jsem vynechal kvůli účasti na doprovodném programu.

Petr Maňas – Zdrojař a motivátor

  • člověk za TEDx Prague
  • TED ED – celosvětový zdroj tutoriálů
  • Ilja Prigogine – Teorie disipativních systémů
  • organizování dobrovolnických akcí: - začít v pohodě, - čím blíže k akci, tím více styl diktátor, - po akci se (ze začátku) 2 měsíce nemohli lidé z pořadatelského týmu vidět 🌟🌟🌟
Zajímavý pohled do zákulisí TEDx.

Pavel Šiška – Future of Work

  • opět názor, že s náhradou lidské práce roboty to tak horké nebude – "robot se vám pod umyvadlo v koupelně zatím nedostane"
  • doporučená kniha: Robots and the Future of Work
Jinak vesměs PR a povídání o Deloitte. 🌟🌟

Jan Hubík – Všichni jsme kyborgové, a možná budeme roboti.

  • člověk s implantovaným čipem v ruce (pozn. později jsem našel, že natočil i rozhovor s DVTV)
  • pohled na techniku jako na něco, co posouvá hranice lidského těla, zamyšlení, co na člověku vylepšuje smartphone: paměť, intelekt, záznam komunikace, orientaci, přístup na web, výpočty, komunikace, teleprezence
  • BBC žebříček: will robot take your job?
  • budoucnost: eliminace přirozené smrti – genová terapie, fixnutí genů stárnutí, zamezení stárnutí tkání a orgánů nanoroboty, záznam vědomí do počítače
Začátek ještě ušel, ale konec už mi připadal hodně z pohádky. A i když by se něco takového podařilo jednou realizovat, postrádám opět smysl, o kterém nepadla zmínka. Přednáška básnící jen o technologii bez snahy o zamyšlení nad společenskými důsledky je jalová, stejně tak jako komentář slovy "zní to šíleně, ale před 20 lety by jiné věci zněly taky šíleně". Lidské tělo považuji za víc než jen nástroj, popř. počítač. 🌟

Martin Palička – Firemní kultura nad zlato?!

  • nesvazovat zaměstnance v oblastech, kde jsou efektivnější, když mají možnost volby - apple x dell
  • týmové pohovory důležité, ale musí být k věci, "ne strojené jako návštěvy Zemana na školách"
  • 90% věcí, které nazýváme urgentními, jsou komplikováním
  • dohody jednoduché, ale trvat na dodržování – je (pozitivní a výchovný) rozdíl říct "nedodržuješ dohodu" oproti "neplníš direktivu"
  • Sigmund Freud: "většina lidí nechce svobodu, protože svoboda znamená zodpovědnost a té se bojí"
  • lidé chtějí více informací, ale nechtějí rozhodovat
  • alibismus "mělo by se"
Plus promo video na Etneteru. Objektivně nahlíženo to byl PR talk o Etneteře, ale mně to nevadilo – Etneterou jsem si prošel před 8 lety, mám ji rád a fandím jim. 🌟🌟🌟🌟

Petr Ludwig – Co ví věda o spokojenosti v práci?

  • autor knihy Konec prokrastinace, stojí i za portálem GrowJob
  • tři motivy, proč děláme práci: 1. job + $ (pro peníze), 2. career (pro prestiž), 3. calling (pro užitek okolí)
  • každé zaměstnání lze dělat jen pro jeden z uvedených motivů, např. lékař + career = dělá mu dobře být v bílém plášti a oslovován pane doktore, uklízečka + calling = ví, že snižuje riziko infekce na operačním sále
  • job crafting = jak z člověka vykřesat smysl práce (studie na Stanfordu, zřejmě toto)
  • IKIGAI – japonský termín označující naplnění 4 základních faktorů tvořících spokojenost v práci: 1. jsme v tom dobří, 2. baví nás to, 3. pomáhá to světu, 4. uživí nás to. Zajímavé i pro použití jako typologie případů, kdy nejsou všechny 4 podmínky splněny (pozn. diagram v prezentaci není korektní Vennův diagram pro 4 množiny, ten je zde)
  • s rostoucími penězi roste spokojenost jen chvíli a pak má tendenci být konstantní
  • Losadův poměr – 3 pozitivní zprávy na 1 negativní
  • naučená bezmocnost – podobný experiment uváděl s křečkem
  • základní managerská poučka: umět lidi nachytat, že udělali něco dobře
Jedno z nejlepších povídání, IKIGAI diagram posluchače dost chytil. Přednášející byl taky jeden z mála, kdo zdůrazil význam vlivu rodiny (vedle školy a firmy) jako jednoho z faktorů, které ovlivňují společnost. 🌟🌟🌟🌟🌟

Tomáš Hrivnák – Brand Strikes Back

  • logoterapie (Viktor Frankl) – lidská potřeba smyslu
  • hlavním zdrojem existenciální úzkosti je pociťovaný nedostatek smyslu
  • když není smysl, frustrovaná vůle ke smyslu se mění na vůli k moci nebo ke slasti (korespondovalo s job + career v předchozím talku)
  • "nedělní neuróza" – tíživý stav před vrácením se do práce, kterou nemáme rádi
  • brand není pouze logo a marketing, ale interpretační rámec pro zorientování se ve firmě, něco jako turistická značka
První část talku šla víc na hloubku a vnímám ji jako zajímavou. Byla oslím můstkem pro druhou část, která se týkala problematiky brandu – hlavního businessu agentury Hrivnák a trochu zas PR. Řečník ale talk zkrátil o to, o co předchozí řečníci přetáhli své talky. Celkově výborný talk. 🌟🌟🌟🌟

Závěr


Na konferenci jsem si náramně užil zdravý diskomfort plynoucí z toho, že jsem ajťák a témata se netýkala přímo IT. Tady zkrátka nešlo podívat se na slajd, představit si, co na něm ten Java kód, XML soubor nebo malůvka se šipkami dělá a pak případně zajít za přednášejícím a poplácat se, že je to fajn, případně, že by to fungovalo i alternativně. Úplně ze začátku se mi na konferenci nechtělo, protože jsem si myslel, že bude úzce specializovaná na problematiku HR a recruitingu (i na vzorku návštěvníků bylo vidět, že to nejsou pouze ajťáci a ani pouze ejčáři).

Strašně proto oceňuju, že to bylo právě naopak – témata zacházela do psychologie, sociologie, filozofie, spirituality i politiky a nic nebylo tabu. Myslím, že to pěkně demonstruje očekávání, které nejen na nás ajťáky budoucnost klade – čím dál tím důležitější budou nejen hard skills pro vlastní obor, ale i schopnost přesahu do jiných oborů a syntézy.

Dalším pěkným zážitkem bylo pozorovat, jak si někteří řečníci navzájem protiřečí, například co se týká prognóz o nástupu robotů nebo důležitosti zavádění některých změn.

Tak jako jsem pochválil přesah konference do výše jmenovaných oborů, zas musím bohužel jmenovat jiné oblasti, kterých se přednášející nedotkli vůbec, nebo jen okrajově, pokud ne rovnou s despektem. Mám na mysli etiku, morálku, sociální aspekty, antropologii a částečně ekonomii. Nepochybuji, že všichni přednášející jsou technicky i softskillově velmi schopné osobnosti. Nicméně ani oni, ani návštěvníci konference nejsou typický vzorek populace. Na jejich projevech jsem postrádal empatii, s níž by se svými myšlenkami obstáli před průměrným občanem. Z některých myšlenek cítím jakousi skrytou, plíživou totalitu – zvýhodnění schopných, kreativních, nerutinních. V kontrastu s nekonkrétním osudem těch "ostatních", o němž téměř nemluvili, ale které v reálu budou tvořit masu. Jakou budou tito lidé "na virtuální realitě" nebo nepodmíněném příjmu prožívat radost a životní smysl v kontextu všech prezentací o prolínání práce se životem?  Úroveň společnosti se pozná podle toho, jak se chová k nejslabším členům, jak se postavíme proti rozevírání "sociálních nůžek"?

Druhým, už méně častým fenoménem, který mne neoslovil, byla určitá nabubřelost v prognózách vzdálenější budoucnosti. To přičítám několika důvodům: snaha zatraktivnit talk (hlavně pro čtenáře scifi), nezohlednění faktoru zla v člověku (libertariánské myšlenky bezstátního zřízení) a optimistické odhady povzbuzené dosavadním pokrokem (asi jako když se po přistání na Měsíci předpovídalo jeho brzké osídlení nebo jak si tvůrci filmu Návrat do budoucnosti 2 představovali rok 2015).


Co na úplný závěr? Konferenci o budoucnosti považuji za úspěšnou do té míry, do jaké dokáže napojit prognózy na přítomnost a historii. Definici inteligence člověka jako schopnosti poučit se z minulosti lze přenést i na lidský druh jako celek. Rád bych znovu vyslovil velký respekt organizačnímu týmu TopMonks a protože věřím, že konference bude mít další ročníky, mohlo by být velmi zajímavé tyto prognózy sbírat a po letech si ověřovat, které se skutečně vyplnily...

22. 5. 2016

Když testy padají při určitém pořadí

Jednou ze základních vlastností dobrého testu je izolovanost. Není přípustné, aby úspěch testu závisel na předchozím volání jiného testu. V jistém smyslu test připomíná pure funkci ve funkcionálním paradigmatu - nemá vedlejší efekty.

V případě čistých unit testů věřím, že s tím většina Java programátorů problém nemá. Máme JUnit, jasně dáno, že instance testovací třídy se vytváří pro každý test, anotacemi řízený setup a teardown a hotovo, není skoro co zkazit.

O něco horší situace je u integračních testů postavených na inicializaci testovacího Spring kontextu. V tomto případě se totiž jednou vytvořený Spring kontext recykluje pro každý test a tím překračuje hranice izolace testu. To je samozřejmě pragmatické nejen z důvodu celkového času testů, ale i proto, že graf vzájemně proinjektovaných Spring-managed bean je pro většinu testů neměnná struktura a těch pár testů, které do ní vrtají, lze pořešit dodatečně pomocí @DirtiesContext. Nicméně z pohledu teoretických pouček je to již ústupek.

Jedním z dalších takových ústupků může být použití aspektů. A že nesprávné použití není na první pohled znát, ukazuje příklad následujícího čerstvě vyřešeného problému z praxe.

Problém

Teamcity reportuje u jedné testovací třídy zfailování jedné metody. Ostatní metody i testy procházejí. Samotný test ovšem v IDE také prochází. Po reprodukci stejného pořadí jako v Teamcity a minimalizaci problému docházím k následujícímu chování:
  • spustí-li se po sobě testy A,B,C, test C zfailuje
  • spustí-li se po sobě testy A,C, test C skončí úspěšně
  • spustí-li se po sobě testy B,C, test C skončí úspěšně
  • spustí-li se po sobě testy B,A,C, test C skončí úspěšně

Popis aktérů

  • A je integrační test, který inicializuje Spring. Součástí Springu je cache manager s cachemi používanými z metod označených anotacemi @Cacheable a @CacheEvict.
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {"classpath:applicationContext.xml"})
    public class A {
    
        @Test public void test() {}
    
    }
    
  • cachování je realizováno přes AspectJ způsobem "compile-time-weaving". Tj. metody s cachovacími anotacemi jsou již při překladu modifikovány tak, že veškerá práce aspektu je zapečena v bytekódu, který obaluje původní tělo metody. Součástí této práce je vyhledání cache manageru v kontextu Springu, nicméně není-li kontext k dispozici, tiše se pokračuje dál a operace se efektivně redukuje na provolání původní metody.
  • B je obyčejný unit test, který ověřuje činnost servisky pro data vrácená z DAO. Metody FooService.method() i FooDao.get() jsou cachované.
    public class B {
    
        @Test
        public void test() {
            Entity x = new Entity("X");
            FooDao fooDao = mock(FooDao.class);
            when(fooDao.get()).thenReturn(x);
            FooService fooService = new FooService();
            fooService.setFooDao(fooDao);
            Object result = fooService.method();
            ...
        }
    
    }
    
  • C je obyčejný unit test. Používá stejný setup FooService a FooDao jako test B až na to, že metody učí mock FooDao na hodnoty Y1, Y2 a u 2. testcasu se očekává výjimka. Anotace @CacheEvict zajišťuje, aby se metody v rámci testovací třídy navzájem neovlivňovaly.
    public class C {
    
        @Test
        @CacheEvict(value = "FooCache", allEntries = true)
        public void test1() {
            Entity y1 = new Entity("Y1");
            FooDao fooDao = mock(FooDao.class);
            when(fooDao.get()).thenReturn(y1);
            FooService fooService = new FooService();
            FooService.setFooDao(fooDao);
            Object result = fooService.method();
            ...
        }
    
        @Test(expected = SomeException.class)
        @CacheEvict(value = "FooCache", allEntries = true)
        public void test2() {
            Entity y2 = new Entity("Y2");
            FooDao fooDao = mock(FooDao.class);
            when(fooDao.get()).thenReturn(y2);
            FooService fooService = new FooService();
            fooService.setFooDao(fooDao);
            Object result = fooService.method();
            ...
        }
    
    }
    
Poznámka: příklady jsou minimalizované, ve skutečnosti je část logiky v setupu, testy se od sebe více liší v setupu i verifikační části, test C je vysušen parametrizací, metody FooService.method() i FooDao.get()mají parametry a cachovací anotace mají příslušné klíče - tolik jen abych neodváděl pozornost puristů od tématu :-).

Příčina

Co se tedy dělo v případě "nešťastného" sledu testů?

  • Na testu A je podstatná pouze inicializace springového kontextu, především beany typu CacheManager.
  • Test B je obyčejný unit test, který tvoří vlastní instanci FooService. Mohlo by se tedy zdát, že nemá se Springem nic společného. Omyl – díky compile-time weavingu je třída obohacena o cachovací aspekt, který je přímo propojen se springovým cache managerem. Protože spring je již nainicializován, cache se použije a výsledkem volání testu je kromě úspěchu i naplnění cache pro metodu method hodnotou resultX.
  • Test C je obyčejný unit test, který nejprve volá metodu test1. Ta se ovšem vůbec nedostane k tomu, aby použila mock DAO, protože volání metody method vytáhne cachovanou hodnotu resultX a pro ní selžou příslušné asserty.
  • Protože test1 skončí výjimkou, neprovede se vyčištění cache anotací @CacheEvict.
  • Zavolá se metoda test2 testu C. Vzhledem ke "špinavé" cachi se opakuje stejná situace. Zde se ovšem výjimka očekává (pozn. v našem případě se jednalo zrovna o stejnou třídu výjimky, při nestejné třídě by tato divergentní situace opět pokračovala), test proto skončí úspěšně a konečně se vyčistí cache.
  • Pokud by následovaly další testy, už by proběhly korektně bez ohledu na to, zda se u nich očekává normální doběhnutí či výjimka.

Proč ostatní sledy testů proběhly dobře?


  • Pokud se vynechal test A, neinicializoval se Spring a tudíž se nepoužívala cache. Všechna volání jinak cachované servisní metody tedy reálně do metody vstoupila (a získala správná testovací data ze svého mock DAO).
  • V případě kombinace AC byl test C prvním, který byl spuštěn po inicializaci Springu, takže dostal čistou cache a díky anotaci @CacheEvict ji průběžně mazal. Problém nicméně i v této kombinaci existoval a byla by to jen otázka konstrukce vhodného testu D, který by na něj upozornil.

Shrnutí

Když záleží na pořadí testů, je to nepříjemné a není to žádná zábava. Jaké zkušenosti si z této lekce odnést?
  • Aspekty na způsob "compile-time weaving" dělají z POJO něco, co už POJO není. Místo obyčejné beany máme něco, co závisí na org.springframework.
  • Uvedené použití anotace @CacheEvict v testech není vhodné. Defaultní hodnota beforeInvocation = false znamená, že aspekt se vyvolá po skončení testu, ale až v dokumentaci se dočteme, že je tomu tak pouze pokud test neskončil výjimkou.
  • Neměli bychom vyhráno ani při uvedení beforeInvocation = true, protože by cache zůstala špinavá po posledním testu (anotace neumožňuje 2 nezávislé přepínače beforeInvocation a afterInvocation).
  • Řešením je po testu smazat cache. Aktuálně sice máme na projektu třídu, která je předkem testů a v @After metodě takové mazání provádí, byla však postavena pouze na znalosti injektovaného Spring cache manageru, což umožňovalo ji použít pouze u springových integračních testů. Při použití v prostých unit testech není k dispozici springový kontext, a přesto chceme přistupovat na cache (samozřejmě pouze je-li dosud inicializována). Naštěstí se lze na cachovací aspekt dostat přes statickou metodu aspectOf:
    AnnotationCacheAspect cacheAspect = AnnotationCacheAspect.aspectOf();
    CacheManager cm = cacheAspect.getCacheManager();
    if (cm != null) { // cache was initialized before
        Collection cacheNames = cm.getCacheNames();
        for (String cacheName : cacheNames) {
            cm.getCache(cacheName).clear();
        }
    }
    
  • Třída není jediný způsob řešení a má své nevýhody (nedefinované pořadí @After anotací, zabetonování hierarchie k třídě, která má spíš povahu traitu). Alternativním řešením je použití JUnit rules, princip však zůstává stejný.

2. 3. 2016

Poznámky z instalace Docker Toolbox na Windows 8

Začínám s Dockerem a dal jsem si za cíl zprovoznit docker-machine. Bohužel Windows není pro Docker systém jaksi úplně nativní, i když za poslední měsíce došlo ke značnému pokroku. Samotný postup instalace je jednoduchý, tutoriál na docker.com vodí za ručičku. Stačí začít zde a postupovat po wizardu až do konce. Nicméně troubleshooting mi dal některé zkušenosti, které chci zachytit:

Virtualizace

na mém ntb nebyla zapnutá, ale po rebootu do BIOSu se checkbox snadno najde a zapnutí se projeví ve Windows přesně dle tutoriálu.

VPN

Při buildování vlastního image se nedařilo připojení na archive.ubuntu.com. Odpověď nalezená na SO mi pak pomohla si uvědomit, že jsem na VPN, neboť problém s DNS jsem před časem kvůli VPN taky řešil. Stačilo se tedy jen odpojit. Jinak nefungující apt-get v Dockerfile je možné zkoušet v dockerovém linux terminálu napřímo, není nutné volat docker run.

403 při docker login

V kroku přihlášení se k Docker hubu akce selhávala po nezvykle dlouhém timeoutu na chybu 403 Request forbidden by administrative rules. Návody, které radily volat docker login s URL registry nebo editovat soubor .docker/config.json, se nakonec ukázaly jako zavádějící, řešení bylo prozaičtější: upgradnout Docker Toolboxu z 1.9 na 1.10. Jsou to všechno relativně mladé issues a udržovat si nejnovější verzi se vyplatí. Po upgradu už to bylo ok.

Upgrade Docker Toolboxu

Docker Toolbox aktuálně nemá mechanismus automatických aktualizací, i když issue na zlepšení v tomto směru existuje. Podle oficiálního návodu se upgrade provede prostým nainstalováním nové verze. To však v mém případě vedlo na chybu startu docker machine An error occurred trying to connect ConnectEx - No connection could be made because the target machine actively refused it. a Machine state changed to 'PoweredOff'. Protože na ni jsem vesměs nic nenašel, odinstaloval jsem nový Docker Toolbox a pro jistotu i VirtualBox a rebootnul počítač. Pak už instalace nové verze i spuštění proběhlo bez problému.

DNS

Chyby apt-getu jako např. Err http://archive.ubuntu.com ... InRelease nebo W: Failed to fetch http://archive.ubuntu.com/ smrdí problémem připojení k internetu. Obzvlášť když na jedné síti (doma) to jde a na jiné (na firmě) už ne. Zabývá se tím toto issue a mně pomohlo otevřít síťová propojení, najít virtuální síťovku nainstalovanou s VirtualBoxem a ve známém okně Vlastnosti vyplnit Internet Protocol Version 4 -> Preferred DNS server 8.8.8.8 (případně Alternate 8.8.4.4). Restart VirtualBoxu. Zřejmě by šly i ostatní tipy (přepínač -dns - ten je ale jen u docker run, ne už u docker build, DOCKER_OPTS nebo nastavení etc/resolv.conf).

Asociace názvu s docker machine

Pro přístup k docker machine přes jméno a ne přes IP je třeba přidat do souboru c:\windows\system32\drivers\etc\hosts záznam pro příslušnou IP. Tu zjistíme buď při startu Docker Toolbox klienta z hlášky docker is configured to use the default machine with IP 192.168.99.100, nebo v shellu boot2dockeru (spuštěném pro danou docker machine z GUI VirtualBoxu přes Start -> Normal Start) příkazem ifconfig nebo příkazem docker-machine ls. Ověřit přes ping docker nebo ssh docker@docker, default heslo tcuser.

Přístup k adresářům na fyzickém stroji

Přepínač -v ve volání docker run -v $PWD:/somedirectory vezme adresář v docker virtuálce zadaný před dvojtečkou (zde aktuální) a zviditelní ho v kontejneru pod cestou zadanou za dvojtečkou. V prostředí Windows ale docker virtuálka běží pod VirtualBoxem, a proto se musí nastavit ještě sdílení ve VirtualBoxu a mount adresáře z docker virtuálky. Celý návod včetně příslušných příkazů a screenshotů je v tomto výborném článku.

Automatický mount v souboru profile

VirtualBox defaultně zpřístupňuje pouze domovský adresář uživatele. Tento setup byl ale pro účely našeho projektu nevhodný, protože docker potřeboval vidět na adresář s projektem, který leží mimo můj domovský adresář v /c/java/workspace/myproject. Jak plyne z předchozího oddílu, pro ustanovení propojení Windows-virtuálka musí být adresář namountován, To lze udělat přepnutím se do konzole shellu virtuálky (nebo z Docker toolbox klienta přes ssh) a spuštěním příkazu sudo mount -t vboxsf -o uid=1000,gid=50 somedirectory /c/java/workspace/myproject/ (sudo, protože jsme pod uživatelem docker, ne pod rootem). Následně lze zkontrolovat přes mount df -h. To je ale nutné dělat při každém startu klienta znovu. Alternativní možnost je proto spouštět mountování v souboru /mnt/sda1/var/lib/boot2docker/profile (bližší popis viz předchozí odkaz).

Výhodou tohoto setupu je, že přežije restart docker-machine. Musí se opakovat pouze po restartu celého VirtualBoxu. Vzhledem k jiným pozdějším zásahům do VirtualBoxu, které vyžadovaly restart, jsem proto variantu přes profile soubor považoval nejdříve za nezajímavou (nastavení se ztrácelo), ale teď už to vypadá stabilně.

Jak ověřit, zda už je na obsah adresáře vidět? Pokud trvá docker run dlouho a na nesprávné namountování adresáře přijde až pozdě, pak je takové zkoušení zdlouhavé. Není však nutné se zdržovat opakovaným voláním původního docker run, místo toho lze buď spustit kontejner v interaktivním režimu (docker run -i -t -v $PWD:/somedirectory myproject /bin/bash) anebo zkusit jiný kontejner, jehož spuštění tak dlouho netrvá (typicky připravené ubuntu - docker run -v $PWD:/somedirectory -it ubuntu bash). V obou případech pak vypsat ls somedirectory.

Perlička: při rozběhávání jsem udělal zkušenost s union file systémem, který tvoří stavební kámen Dockeru. Po jednom nezdařeném pokusu, kdy se ještě adresář nenamountoval, jsem chtěl smazat prázdný adresář /c/java/workspace/myproject v shellu virtuálky. Věděl jsem sice, že nemám explicitně namountovaný jiný adresář, ale zapomněl jsem na automatický mount domovského adresáře. Proto se rm -rf /c/ pokusil smazat celý domovský adresář. Naštěstí na věci mimo Docker neměl práva, proto se celkem nic nestalo - schytal to však právě adresář .docker, kde příkaz smazal systémové soubory a po podivných chybách jako např. Could not read CA certificate ... ca.pem: The system cannot find the file specified. jsem usoudil, že nejlepší bude celý Docker Toolbox přeinstalovat.

Problém s chybějícím dvojitým lomítkem jsem však neměl.
Více o volumes.

Symlinky

Máme konečně přístup do adresáře, ale ještě není vyhráno, Docker chce vytvářet symbolické linky, ale nedaří se, symptomem jsou chyby jako run EROFS: read-only file system, symlink a samozřejmě failující docker run. Řešení má 2 kroky: (1) nastavit příslušný parametr VirtualBoxu, který povoluje symlinky pro daný adresář: VBoxManage setextradata default VBoxInternal2/SharedFoldersEnableSymlinksCreate/somedirectory 1 a (2) spustit VirtualBox jako administrator.

Kroky jsem aplikoval v uvedeném pořadí, takže už jsem neověřoval, zda by problém nebyl vyřešen pouze tím druhým. Pro příkaz setextradata existuje i ekvivalent getextradata (bez hodnoty na konci) pro ověření efektu. Protože ke spouštění VirtualBoxu dojde automaticky z Docker Toolboxu, musí se i Docker Toolbox spouštět jako administrator, ideálně to nastavit přímo ve vlastnostech zástupce. Docker machine default, která běží ve VirtualBoxu spuštěném jako administrator, se ve VirtualBoxu spuštěném normálně jeví jako vypnutá!

Pro ověření funkčnosti opět není potřeba opakovat případnou dlouhotrvající operaci, stačí nastartovat kontejner jako v předchozím odstavci a zkusit vytvořit link pomocí ln -s existingfile.txt linkname.txt.

Ostatní tipy

Smazání kontejneru

Smazat image příkazem docker rmi imagename není dobré, pokud nad imagem běží kontejner. Ve výpisu docker images zůstane image viset s <null> jménem. Použití rmi -f přes hash sice image smaže, ale kontejnery to samo o sobě nesestřelí! Je proto lépe nejdřív vypsat si kontejnery pomocí docker ps, stopnout příslušný kontejner pomocí docker stop a až pak teprve mazat image. Pokud už jsme smazali image, kontejnery stopnout dodatečně, užitečný je hromadný příkaz docker rm $(docker ps -a -q).

Ostatní zajímavé odkazy