Článek popisuje využití techniky zvané Continuous Integration (CI, česky průběžná integrace) pro systém postavený nad .NET Frameworkem a vyvíjený ve Visual Studiu 2005. Průběžné integrace je dosaženo pomocí nástroje CruiseControl.NET.
reklama
Úvod do problému
Ve mé domovské firmě vyvíjíme ve větším počtu lidí dlouhodobý projekt postavený nad .NET Frameworku. Před zavedením CI jsme bojovali se snižující se kvalitou a zvětšující se náročností buildů. Časté vytváření průběžných stabilních buildů, které jdou dle dohody přímo k zákazníkovi, tým značným způsobem zatěžovalo. Vytvoření buildu bylo časově náročnou záležitostí, což nám při cca dvoutýdenních vývojových iteracích způsobovalo velké problémy – každou iteraci se našlo několik lidí, kteří většinu času namísto vývoje řešili problémy buildu.
Tato situace se nám samozřejmě nelíbila, proto jsme se snažili nalézt její příčiny. Ty jsou podle nás následující:
- Pozdní nalezení chyb – na chyby se většinou přicházelo až při funkčních testech buildu, což však nutně znamenalo po opravě chyby zdržení kvůli vytvoření nového buildu. Častokrát šlo o chyby, které byly odhalitelné správnými unit testy.
- Vysoká chybovost kódu – ve srovnání se zkušenostmi z jiných projektů bylo v kódech v průměru více chyb.
- Chybový proces buildu a nasazení – build a následně i jeho nasazení bylo zajišťováno manuálně. Přesto, že nešlo o nic složitého - celkem asi 10 jednoduchých kroků - často nastávaly situace, kdy se na některý z kroků pozapomnělo a build se kvůli tomu nepovedl. Na toto se častokrát opět přišlo až během funkčních testů, z čehož plynulo další zdržení.
- Zmatek ve verzích – build, která jde k zákazníkovi, není vždy správně označen verzí a spárovaný s kódy v CVS (nástroj pro správu zdrojových kódů), což je většinou důsledek chybného postupu při manuálním vytváření buildu. Občas je však potřeba i na nasazené verzi opravit chybu, což je v tomto případě velice obtížné – schválně si zkuste dohledat kódy v CVS, když je nemáte označeny tagem...
Všechny výše uvedené nemoci vývoje jsme se rozhodli léčit ozdravnou kůrou s hlavním lékem nazvaným průbežná integrace.
Co je to "Průběžná integrace"?
Možná se ptáte, co to vlastně ta průběžná integrace je? Asi nejlépe ji definuje sám jeden z jejich autorů, známý softwarový evangelista Martin Fowler:
„Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily - leading to multiple integrations per day. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible.“ (viz. článek Continuous Integration), tedy
„Průběžná integrace je praktika softwarového vývoje, která vyžaduje po členech vývojového týmu častou integraci svých zdrojových kódů. Každý člověk většinou integruje nejméně jednou denně, což má za následek více integrací za den. Každá integrace je ověřena automatizovaným buildem (včetně testů), aby byly integrační chyby odhaleny co nejdříve.“.
Na našem projektu to v praxi znamenalo zavedení následujícího procesu:
- Po každém uceleném commitu do CVS je automaticky spuštěn build. Commit je prohlášen za úspěšný až ve chvíli, kdy build proběhne bez chyby. Je povinností vývojáře jakoukoliv chybu zjištěnou při buildu okamžitě vyřešit – tj. v praxi to znamená, že nejde domů, dokud není build provozuschopný.
- Součástí buildu jsou následující kroky:
- Z CVS se stáhnou kompletní zdrojové kódy
- Zdrojové kódy se přeloží
- Nad přeloženými kódy se spustí unit testy
- Vytvoří se build ve formě vhodné k nasazení (v našem případě webová aplikace)
- Úspěšný build je automaticky nasazen a je okamžitě dostupný pro testery. Zároveň je v CVS označen jedinečným tagem, tudíž jsme schopni se k němu kdykoliv vrátit.
- Informace o úspěchu či neúspěchu buildu je ihned reportována všem zúčastněným osobám.
Naštěstí existují nástroje, jako například Draco.NET, CI Factory, Team Foundation Build či CruiseControl.NET, které umožňují výše uvedený proces přivést do praxe s poměrně malým úsilím. Kvůli dobrým referencím a kvalitní dokumentaci jsme se rozhodli použít poslední jmenovaný.
V dalším textu představím CruiseControl.NET o něco podrobněji a ukážu, jak jej lze velice snadno nakonfigurovat tak, aby vytvářel průběžné buildy přímo z vývojové workspace uložené v CVS.
Úvod do CruiseControl.NET
CruiseControl.NET, dále označováno také CCNET, je známý nástroj pro zajištění průběžné integrace nad platformou .NET a je považovaný za de-facto standard v automatizaci buildů na této platformě. Vyvíjen je společností ThoughtWorks, která je mezi širokou vývojářskou veřejností známá mimo jiné jako domovská firma Martina Fowlera, jedná se tudíž o nástroj přímo od pramene.
Klíčové vlastnosti CruiseControl.NET jsou
- Podpora řady systémů pro správu zdrojových kódů jako je CVS, Subversion, Visual Source Save a další
- Podpora dalších externích nástrojů jako je Nant, MSBuild, případně libovolné command-line utility
- Možnost vytváření více různých buildů zároveň
- Vzdálená správa a reportování
Pro potenciálního uživatele neméně užitečnou vlastností je cena, která je rovna přesně 0 korunám českým, CruiseControl.NET je totiž open source. Navíc je distribuován také ve formě grafické instalace, takže stačí pouze pár stisků tlačítka a lze začít experimentovat.
Samotný CCNET je většinou nasazen ve formě Windows Service běžící na dedikovaném integračním serveru. Společně s CCNET je na integrační server nainstalován i nástroj DashBoard, pomocí něhož je možné zobrazit si aktuální informace o všech buildech včetně kompletní historie minulých buildů a doplňujících statistik.
Konfigurace CCNET
Proces buildů řízených přes CruiseControl.NET se nastavuje prostřednictvím XML konfiguračního souboru ccnet.config, který se nachází v instalačním adresáři CCNET a je dostupný také pod zástupcem z nabídky Start. Pro každý build je zde uvedena samostatná sekce <project>, obsahující řadu dalších direktiv pro řízení daného buildu.
V následujícím textu ukážu a vysvětlím konfiguraci, kterou používáme v praxi. Příklad téměř plně odpovídá reálné konfiguraci, hlavní rozdíl je ve způsobu nastavení buildu před jeho nasazením, který je v reálu o něco složitější a vyžaduje více vstupních parametrů. Pro jednodušší projekty však bude předváděná konfigurace plně dostačovat.
Kompletní konfigurace
Rozhodl jsem se začít ukázáním kompletní konfigurace a teprve v dalších odstavcích budu postupně vysvětlovat smysl jednotlivých konfiguračních sekcí. Pojmenování konfiguračních elementů a atributů je však natolik intuitivní, že podrobnější popis skoro není potřeba. Nakonec, posuďte sami:
<project name="STP">
<!-- Základní parametry -->
<workingDirectory>
d:\ccnet\projects\STP\WorkingDirectory
</workingDirectory>
<artifactDirectory>d:\ccnet\projects\STP\Artifacts</artifactDirectory>
<modificationDelaySeconds>120</modificationDelaySeconds>
<!-- Generování tagu -->
<labeller type="defaultlabeller">
<prefix>1.0.4.</prefix>
<incrementOnFailure>false</incrementOnFailure>
</labeller>
<!-- Komunikace se repositářem zdrojových kódů (CVS) -->
<sourcecontrol type="cvs">
<executable>
c:\Program Files\CruiseControl.NET\tools\cvswithplinkrsh.bat
</executable>
<cvsroot>:ext:vige53@192.168.80.5:/usr/data/CVS/STP</cvsroot>
<module>ALGORITHM-API ALGORITHM-IMPL ...</module>
<labelOnSuccess>true</labelOnSuccess>
<workingDirectory>
D:\ccnet\Projects\STP\WorkingDirectory
</workingDirectory>
</sourcecontrol>
<!-- Základní sekvence akcí pro dosažení buildu -->
<tasks>
<!-- Přeložení zdrojových kódů -->
<devenv solutionfile=
"d:\ccnet\projects\STP\WorkingDirectory\Solution\STP-build.sln"
configuration="debug">
<executable>
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.com
</executable>
</devenv>
<!-- Spuštění unit testů -->
<nunit path="C:\Program Files\NUnit 2.4.3\bin\nunit-console.exe">
<assemblies>
<assembly>
d:\ccnet\...\ALGORITHM-TEST\bin\Debug\ALGORITHM-TEST.exe
</assembly>
</assemblies>
</nunit>
<!-- Nasazení buildu -->
<buildpublisher>
<sourceDir>e:\ccnet\projects\STP\WorkingDirectory\WEB</sourceDir>
<publishDir>c:\inetpub\wwwroot\STP</publishDir>
<useLabelSubDirectory>true</useLabelSubDirectory>
</buildpublisher>
<!-- Závěrečná konfigurace -->
<nant>
<executable>C:\Program Files\nant\bin\Nant.exe</executable>
<buildFile>D:\ccnet\Projects\STP\stp-nant-ccnet.build</buildFile>
<buildArgs>-D:build_dir=:\inetpub\wwwroot\STP</buildArgs>
<targetList>
<target>configure_deployment</target>
</targetList>
</nant>
</tasks>
<!-- Postbuild tasky -->
<publishers>
<xmllogger />
<statistics />
</publishers>
</project>
Základní parametry
Jak jsem už zmiňoval, každý projekt v CCNET je reprezentován konfiguračním elementem <project>. Povinně je nutné vyplnit atribut name udávající jméno projektu.
V elementech workingDirectory a artifactDirectory CCNETu říkáme, jaký má používat pracovní adresář respektive do jakého adresáře bude generovat výstupy buildu jako jsou logy, výsledky unit testů či statistiky.
Generování čísla verze
Pro každý úspěšný build potřebujeme generovat jedinečné číslo verze, které se pak použije na více místech celého procesu buildu. Číslo verze využíváme pro označení jednak zdrojových kódů v CVS (tzv. tagování), tak i pro nastavení verze buildem vytvořených assembly. Navíc chceme číslo verze použít také pro vytvoření jména adresáře s automaticky nasazenou verzí buildu.
Generování čísla verze je nastavitelné v sekci labeller. Námi využívaný výchozí labeller dodávaný s CCNET (defaultLabeller) generuje pro každý build jednoduché celé číslo, které se automaticky při každém buildu inkrementuje. Před takto generované číslo buildu je možné připojit prefix (element prefix) a tím dostat číslo buildu ve standardním čtyřčíselném formátu. Zároveň potlačujeme zvyšování čísel verzí pro neúspěšné buildy (incrementOnFailure).
Získávání zdrojových kódů
Velice důležitou součástí nástroje pro buildy je schopnost automaticky vytáhnout zdrojové kódy z používaného repozitáře zdrojových kódů. V našem případě využíváme pro správu zdrojových kódů nástroj CVS. V konfiguraci buildu tedy používáme blok <sourcecontrol type=“CVS“/>.
Pro CVS je povinně potřeba nastavit v elementu executable cestu k cvs.exe. My ale namísto standardního cvs.exe používáme jednoduchou command-line utilitu, která nám umožňuje využít pro spojení s CVS šifrovaného extssh připojení. Více viz oficiální dokumentace "Using CCNET with CVS".
Další dvě povinné položky jsou cesta k CVS repository (cvsroot) a seznam modulů, které se z repository mají stáhnout (module).
K procesu získávání zdrojových kódů patří i obsah elementu modificationDelaySeconds, která udává počet vteřin, který musí uplynout od posledního commitu, před tím než se spustí automatický build. Naše nastavení v praxi znamená, že se build spustí vždy 2 minuty poté, co vývojář vložil své změny do CVS.
Jak jsem už zmiňoval výše, každý úspěšný build chceme v CVS označit generovaným tagem. Tohoto lze dosáhnout jednoduše pomocí nastavení labelOnSuccess na hodnotu true. Pro tagování se využije číslo verze, které bylo vygenerováno dle nastavení v sekci labeller.
Na tomto místě musím CCNET trochu pohanět, neboť kvůli tomu, aby bylo možné z CVS stahovat více než jeden modul, jsem musel jsem použít verzi 1.2.1 a na ní aplikovat neoficiální patch(k dispozici zde). Pro aktuální verzi 1.3.0 bohužel tento patch neexistuje, takže jediná možnost by bylo upravit si zdrojové kódy a vytvořit vlastní build CCNET, do čehož se mi celkem pochopitelně nechtělo. Naštěstí verze 1.2.1 šla použít bez problémů.
Build
Přeložení zdrojových kódů
Za přeložení zdrojových kódů je zodpovědný konfigurační blok devenv, který pro překlad využívá Visual Studio. Velkou výhodou tohoto přístupu k buildu je naprosto minimální nutnost konfigurace, jelikož lze většinou využít *.sln soubor s konfigurací vývojové workspace. Naopak nevýhodou je nutnost mít na integračním serveru nainstalované Visual Studio. Ale ta jedna licence navíc se více než vyplatí.
Spuštění unit testů
Spuštění unit testů je konfigurováno pomocí nunit sekce. V ní je potřebné uvést seznam všech testovacích assembly. Ve výše uvedeném příkladu jsem schválně kvůli přehlednosti nechal pouze jednu assembly, v reálu jich však máme několik desítek.
Nasazení buildu
Pokud build i unit testy projdou bez chyby, chceme aplikaci zpřístupnit pro funkční testování. Toho dosáhneme zkopírováním vytvořeného buildu do adresáře zpřístupněného testovacímu týmu (ideálně umístěného na odděleném, testovacím serveru) a jeho následnou konfigurací do opravdu spustitelného tvaru.
Samotné nasazení je zajištěno konfiguračním blokem buildPublisher, kterému říkáme zdrojový a cílový adresář, s tím že v cílovém adresáři má vytvořit podadresář s číslem buildu, tak jak bylo vygenerováno labellerem.
Následná konfigurace buildu je v režii na míru vytvořeného NAnt skriptu, jehož detaily nejsou z pohledu tohoto článku důležité. Po konfiguraci je build plně provozuschopný a připraven k funkčnímu testování.
Post-build kroky
Po jakémkoliv úspěšném či neúspěšném buildu se vykonají všechny kroky, které jsou uvedeny v sekci publishers. V našem případě se vygenerují XML soubory používané službou CCNET DashBoard pro zobrazení detailních informací o buildu (element xmllogger). Zároveň se při každém buildu zaktualizují souhrnné statistiky buildů (element statistics) – tyto lze využít pro získání informací o úspěšností jednotlivých buildů, unit testů atd.
Reportování výsledků buildu
Zprávu o stavu buildu je možné z CCNET rozesílat například pomocí e-mailů. My jsme se však namísto toho rozhodli použít nástroj cctray, který zobrazuje stav aktuálního buildu barevnou ikonkou na nástrojové liště. Stačí ho nainstalovat na vývojářské stanice a každý vývojář tak získá okamžitou zpětnou vazbu z průběžných buildů. V praxi to znamená, že pokud ikonka zčervená chvíli poté, co jsem commitnul změny do CVS, je na místě, abych danou chybu řešil, nebo se alespoň o řešení zajímal.
Díky ikonce cctray si neúspěšného buildu okamžitě všimne i management projektu a na základě informací získaných z logu změn mezi jednotlivými průběžnými buildy, zpřístupněného pomocí nástroje DashBoard, může opravu chyby přidělit opravdu tomu, kdo ji způsobil. Jednoduché, geniální.
Závěr
Zapojení metody průběžné integrace do vývojového procesu nám zejména výrazně pomohlo uvolnit si ruce od opakovaného manuálního vytváření buildů. Díky ní jsme schopni dodávat časté buildy bez výraznějšího zvýšeného úsilí.
Velký přínos je také v častém spouštění unit testů, díky kterému odhalujeme chyby dříve a tudíž i jejich opravy jsou méně náročné.
Na úplný závěr musím pochválit nástroj CruiseControl.NET, bez něhož by nastartování procesu průběžné integrace bylo výrazně složitější. Takto se jednalo o záležitost jediného dne intenzivní práce a několik pozdějších drobných úprav konfigurace s ohledem na aktuální požadavky. Nástroj sice není bez nedostatků (viz. chybějící podpora stahování více CVS modulů), ale přesto převládají výrazně kladné dojmy.