<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Cygni</title>
	<atom:link href="http://cygni.se/feed/" rel="self" type="application/rss+xml" />
	<link>http://cygni.se</link>
	<description>expertis inom agil systemutveckling på moderna plattformar</description>
	<lastBuildDate>Wed, 15 May 2013 13:07:03 +0000</lastBuildDate>
	<language>sv</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>Så fixar du buggen med återställda tangentbindningar i Ubuntu 12.10 och 13.04</title>
		<link>http://cygni.se/2013/05/15/sa-fixar-du-buggen-med-aterstallda-tangentbindningar-i-ubuntu-12-10-och-13-04/</link>
		<comments>http://cygni.se/2013/05/15/sa-fixar-du-buggen-med-aterstallda-tangentbindningar-i-ubuntu-12-10-och-13-04/#comments</comments>
		<pubDate>Wed, 15 May 2013 13:07:03 +0000</pubDate>
		<dc:creator>Jon Edvardsson</dc:creator>
				<category><![CDATA[Stacktrace]]></category>
		<category><![CDATA[idea]]></category>
		<category><![CDATA[intellij]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://cygni.se/?p=4826</guid>
		<description><![CDATA[Under några månaders tid har jag stört mig på att Ubuntu återställer tangentbindningar som jag gjort när datorn bootas om. T.ex. så krockar Ubuntus Alt+F7 Move Window med Intellijs IDEAs find. Det verkar vara en bug i 12.10 och 13.04 som introducerade detta. Som temporär lösning på problemet har jag lagt följande i filen $HOME/.gsettings ...]]></description>
			<content:encoded><![CDATA[<p>Under några månaders tid har jag stört mig på att Ubuntu återställer tangentbindningar som jag gjort när datorn bootas om. T.ex. så krockar Ubuntus Alt+F7 Move Window med Intellijs IDEAs find. Det verkar vara en bug i 12.10 och 13.04 som introducerade detta.</p>
<p>Som temporär lösning på problemet har jag lagt följande i filen <code>$HOME/.gsettings</code> som jag sedan läser in i <code>$HOME/.profile</code>.</p>
<pre class="brush: bash; title: $HOME/.gsettings;">
# Check what bindings are available
# gsettings list-recursively  |grep key|less

gsettings set org.gnome.desktop.wm.keybindings move-to-workspace-left &quot;['&lt;Shift&gt;&lt;Control&gt;&lt;Alt&gt;Left']&quot;
gsettings set org.gnome.desktop.wm.keybindings move-to-workspace-right &quot;['&lt;Shift&gt;&lt;Control&gt;&lt;Alt&gt;Right']&quot;
gsettings set org.gnome.desktop.wm.keybindings move-to-workspace-up &quot;['&lt;Shift&gt;&lt;Control&gt;&lt;Alt&gt;Up']&quot;
gsettings set org.gnome.desktop.wm.keybindings move-to-workspace-down &quot;['&lt;Shift&gt;&lt;Control&gt;&lt;Alt&gt;Down']&quot;

gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-left &quot;['&lt;Control&gt;&lt;Super&gt;&lt;Alt&gt;Left']&quot;
gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-right &quot;['&lt;Control&gt;&lt;Super&gt;&lt;Alt&gt;Right']&quot;
gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-up &quot;['&lt;Control&gt;&lt;Super&gt;&lt;Alt&gt;Up']&quot;
gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-down &quot;['&lt;Control&gt;&lt;Super&gt;&lt;Alt&gt;Down']&quot;

gsettings set org.gnome.settings-daemon.plugins.media-keys screensaver '&lt;Super&gt;l'
gsettings set org.gnome.desktop.wm.keybindings begin-move &quot;['&lt;Super&gt;F7']&quot;
gsettings set org.gnome.desktop.wm.keybindings begin-resize &quot;['&lt;Super&gt;F8']&quot;
gsettings set org.gnome.desktop.wm.keybindings show-desktop &quot;['&lt;Super&gt;d']&quot;

gsettings set org.gnome.desktop.wm.keybindings toggle-shaded &quot;['disabled']&quot;
</pre>
<pre class="brush: bash; title: Fragment ur $HOME/.profile;">
if [ -f &quot;$HOME/.gsettings&quot; ]; then
	. &quot;$HOME/.gsettings&quot;
fi
</pre>

	Tags: <a href="http://cygni.se/taggar/idea/" title="idea" rel="tag">idea</a>, <a href="http://cygni.se/taggar/intellij/" title="intellij" rel="tag">intellij</a>, <a href="http://cygni.se/taggar/ubuntu/" title="ubuntu" rel="tag">ubuntu</a><br />
]]></content:encoded>
			<wfw:commentRss>http://cygni.se/2013/05/15/sa-fixar-du-buggen-med-aterstallda-tangentbindningar-i-ubuntu-12-10-och-13-04/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>5 tips för att ta det där Java-certet</title>
		<link>http://cygni.se/2013/03/28/5-tips-for-att-ta-det-dar-java-certet/</link>
		<comments>http://cygni.se/2013/03/28/5-tips-for-att-ta-det-dar-java-certet/#comments</comments>
		<pubDate>Thu, 28 Mar 2013 09:11:24 +0000</pubDate>
		<dc:creator>Frida Hoas</dc:creator>
				<category><![CDATA[Stacktrace]]></category>
		<category><![CDATA[certifiering]]></category>
		<category><![CDATA[ide]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[java programmer]]></category>
		<category><![CDATA[java se]]></category>
		<category><![CDATA[programmering]]></category>
		<category><![CDATA[test]]></category>
		<category><![CDATA[tips]]></category>

		<guid isPermaLink="false">http://cygni.se/?p=4803</guid>
		<description><![CDATA[Som nyexad webbprogrammerare var det inte helt lätt att slänga sig in i certifieringsdjungeln. Det var många nya begrepp och ofta förstod jag varken frågorna eller förklaringarna när jag först började läsa. Även om jag hade haft Java som huvudspråk under min utbildningen var jag inte så van vid att prata om språket och jag hade ...]]></description>
			<content:encoded><![CDATA[<p>Som nyexad webbprogrammerare var det inte helt lätt att slänga sig in i certifieringsdjungeln. Det var många nya begrepp och ofta förstod jag varken frågorna eller förklaringarna när jag först började läsa. Även om jag hade haft Java som huvudspråk under min utbildningen var jag inte så van vid att prata om språket och jag hade definitivt inte ställts inför frågor om regler på hur saker och ting fungerar beroende på om man gör si eller så. Vissa klasser och metoder hade jag inte stött på tidigare och då blev de kodexempel, som egentligen skulle underlätta, ett hinder mer än ett hjälpmedel. Men skam den som ger sig&#8230; nu har jag min Java Programmer-certifiering. Det här är mina tips för hur du lyckas!<span id="more-4803"></span></p>
<h3>Använd en bra studieguide</h3>
<p>Suns  egna certifieringsböcker är guld värda. Varje ämne betas noggrant och  metodiskt av i lagom stora kapitel. Där beskrivs precis vad du behöver veta  för att klara provet med väl förklarande kodexempel. Varje kapitel har  även ett gäng examinationsfrågor där du kan kolla om du snappat upp vad  kapitlet handlat om och en kortare sammanfattning tillsammans med en  punktlista av de viktigaste sakerna. Punktlistans påståenden passar  utmärkt att göra flashcards av för att ha när man repeterar! Håll i åtanke att det kanske inte räcker att läsa boken bara en gång&#8230;</p>
<ul>
<li><a title="SCJP Sun Certified Programmer For Java 6 Exam" href="http://http://www.amazon.com/SCJP-Certified-Programmer-Java-310-065/dp/0071591060/ref=sr_1_2?s=books&amp;ie=UTF8&amp;qid=1363968506&amp;sr=1-2&amp;keywords=Java+SE+6+Programmer+Practice" target="_blank">SCJP Sun Certified Programmer For Java 6 Exam</a></li>
</ul>
<h3>Gå tillbaka till grunderna</h3>
<p dir="ltr">Få  inte panik om begreppen och språken i studieguiden är lite över huvudet. De  flesta som studerar till certifieringen har några års erfarenhet av Java  och programmering och har man inte koll på begreppen är det bättre att  gå tillbaka till något mer grundläggande studiematerial  och reda ut  dessa ord innan man går vidare med certifieringsguiden. Inför provet är  det viktigt att komma ihåg att man inte bara ska kunna koda utan även  prata om kod. Många begrepp handlar just om att beskriva kod eller prata  runt hur man ska koda för att uppnå vissa resultat som kanske har med  prestanda, säkerhet eller arkitektur att göra. Eller bara kunna  definitionen på t ex vad ett interface är. Plugga begreppen, inte bara  koden.</p>
<h3>Gör examinationsfrågor</h3>
<p>Ibland  tror man att man kan något tills man ställs inför en fråga.  Examinationsfrågorna i boken är perfekta för att kolla om man verkligen  har förstått det man tror att man har förstått. När det gäller kod ska  man inte bara ha koll på den rekommenderade standarden, men också alla  möjliga godkända sätt att skriva, även de som inte är särskilt snygga.  Du ska kunna skillnaden på om koden inte kompilerar eller kastar ett  exception, eller bara är fult skriven och inte följer standardsättet men  fungerar!  Testfrågor finns även att få tag i på diverse olika  webbsidor, i mobilappar, eller i Suns egna böcker med  “inför-testet-test”.</p>
<ul>
<li><a title="OCP Java SE 6 Programmer Practice Exams" href="http://www.amazon.com/Programmer-Practice-Exams-310-065-Certification/dp/0072260882">OCP Java SE 6 Programmer Practice Exams</a></li>
<li><a title="SCJP mock test online" href="http://scjptest.com/mock-test.xht" target="_blank">SCJP mock test online</a></li>
</ul>
<h3>Koda</h3>
<p>Ett  bra sätt att lära sig det som man kanske inte stött på så mycket förut är att koda det. Speciellt de områden som handlar om att pussla ihop  flera klasser för att få ihop en funktionalitet; till exempel att  läsa/skriva från fil eller interfacen <code>Comparable</code> och <code>Comparator</code>. Så ge  dig själv lite kodtid med det som känns lite extra svårt. Och testa även  att koda på fri hand, dvs utan en IDE eller en javadoc i närheten. Det  är ett bra sätt att använda hjärnan och se hur mycket du kommer ihåg av  klassernas metoder. Men kolla givetivs upp om du fastnar helt, men då  kan du grejerna sen till nästa gång!</p>
<h3>Fokusera på det som är svårt</h3>
<p>När  du läst boken, pluggat grundläggande begrepp, nött frågor, och kodat  dig trött. Gör ett test med 60 frågor, skriv ner vad vilka områden du  hade fel på och börja om från början igen och plugga på det du  uppenbarligen inte har koll på. Så, starta om, men fokusera på det som  du tycker är svårt. Läs igenom texterna igen, koda ett exempel, försöka  utforska klassen eller ett api genom att fundera och expermintera, vad  händer om jag skriver så här, osv. Iterera samma sätt om och om igen,  tills allt sitter som en smäck.</p>

	Tags: <a href="http://cygni.se/taggar/certifiering/" title="certifiering" rel="tag">certifiering</a>, <a href="http://cygni.se/taggar/ide/" title="ide" rel="tag">ide</a>, <a href="http://cygni.se/taggar/java/" title="java" rel="tag">java</a>, <a href="http://cygni.se/taggar/java-programmer/" title="java programmer" rel="tag">java programmer</a>, <a href="http://cygni.se/taggar/java-se/" title="java se" rel="tag">java se</a>, <a href="http://cygni.se/taggar/programmering/" title="programmering" rel="tag">programmering</a>, <a href="http://cygni.se/taggar/test/" title="test" rel="tag">test</a>, <a href="http://cygni.se/taggar/tips/" title="tips" rel="tag">tips</a><br />
]]></content:encoded>
			<wfw:commentRss>http://cygni.se/2013/03/28/5-tips-for-att-ta-det-dar-java-certet/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cygni är en av Sveriges bästa arbetsplatser 2013!</title>
		<link>http://cygni.se/2013/03/22/cygni-ar-en-av-sveriges-basta-arbetsplatser-2013/</link>
		<comments>http://cygni.se/2013/03/22/cygni-ar-en-av-sveriges-basta-arbetsplatser-2013/#comments</comments>
		<pubDate>Fri, 22 Mar 2013 08:20:36 +0000</pubDate>
		<dc:creator>Jon Persson</dc:creator>
				<category><![CDATA[Cygnibloggen]]></category>
		<category><![CDATA[cygni]]></category>

		<guid isPermaLink="false">http://cygni.se/?p=4791</guid>
		<description><![CDATA[Den 21 mars publicerade det oberoende konsultföretaget Great Place To Work sin årliga lista över Sveriges 15 bästa arbetsplatser. Som nykomling i undersökningen hamnade Cygni på en mycket glädjande och hedrande femteplats! Hela listan finns här. Tags: cygni]]></description>
			<content:encoded><![CDATA[<p>Den 21 mars publicerade det oberoende konsultföretaget Great Place To Work sin årliga lista över Sveriges 15 bästa arbetsplatser. Som nykomling i undersökningen hamnade Cygni på en mycket glädjande och hedrande femteplats!</p>
<p>Hela listan finns <a href="http://www.greatplacetowork.se/baesta-arbetsplatser/sveriges-baesta-arbetsplatser/648-2013">här</a>.</p>
<p><a rel="attachment wp-att-4794" href="http://cygni.se/2013/03/22/cygni-ar-en-av-sveriges-basta-arbetsplatser-2013/gptw_sverigesbastaarbetsplatser2013_rgb/"><img class="alignleft size-full wp-image-4794" title="gptw_SverigesBästaArbetsplatser2013_rgb" src="http://cygni.se/wp-uploads/2013/03/gptw_SverigesBästaArbetsplatser2013_rgb.gif" alt="" width="296" height="115" /></a></p>

	Tags: <a href="http://cygni.se/taggar/cygni/" title="cygni" rel="tag">cygni</a><br />
]]></content:encoded>
			<wfw:commentRss>http://cygni.se/2013/03/22/cygni-ar-en-av-sveriges-basta-arbetsplatser-2013/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Vagrant &#8211; den bästa nyheten sedan skivat bröd</title>
		<link>http://cygni.se/2013/02/26/vagrant-den-basta-nyheten-sedan-skivat-brod/</link>
		<comments>http://cygni.se/2013/02/26/vagrant-den-basta-nyheten-sedan-skivat-brod/#comments</comments>
		<pubDate>Tue, 26 Feb 2013 10:50:38 +0000</pubDate>
		<dc:creator>Tommy Wassgren</dc:creator>
				<category><![CDATA[Stacktrace]]></category>
		<category><![CDATA[chef]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[provisioning]]></category>
		<category><![CDATA[puppet]]></category>
		<category><![CDATA[ubuntu]]></category>
		<category><![CDATA[vagrant]]></category>
		<category><![CDATA[virtual box]]></category>

		<guid isPermaLink="false">http://cygni.se/?p=4766</guid>
		<description><![CDATA[Vagrant är ett verktyg för att på ett smidigt sätt via kommandoraden kunna hantera olika virtuella boxar (Oracle VirtualBox). Vagrant används för att skapa och konfigurera lättviktiga, reproducerbara och portabla utvecklingsmiljöer. Grundkrav VirtualBox &#8211; Installation av VirtualBox sker via nedladdning från https://www.virtualbox.org/wiki/Downloads Vagrant &#8211; Installation av Vagrant sker via Vagrants egen installer eller via följande ...]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.vagrantup.com/" target="_blank">Vagrant</a> är ett verktyg för att på ett smidigt sätt via kommandoraden kunna hantera olika virtuella boxar (<a href="https://www.virtualbox.org/" target="_blank">Oracle VirtualBox</a>). Vagrant används för att skapa och konfigurera lättviktiga, reproducerbara och portabla utvecklingsmiljöer.</p>
<h3>Grundkrav</h3>
<ul>
<li>VirtualBox &#8211; Installation av VirtualBox sker via nedladdning från <a href="https://www.virtualbox.org/wiki/Downloads" target="_blank">https://www.virtualbox.org/wiki/Downloads</a></li>
<li>Vagrant &#8211; Installation av Vagrant sker via Vagrants <a href="http://downloads.vagrantup.com/" target="_blank">egen installer</a> eller via följande gem:
<pre class="brush: bash; title: ;">
$ gem install vagrant
</pre>
</li>
</ul>
<h3>Sätt igång</h3>
<p>Då är vi redo att börja. För att skapa en ny virtuell box med Ubuntu 12.04 LTS kör du bara följande kommandon:</p>
<pre class="brush: bash; title: ;">
$ vagrant box add precise64 http://files.vagrantup.com/precise64.box
$ mkdir -p testenv
$ cd testenv
$ vagrant init precise64
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.
</pre>
<p>Kommandot på rad 1 laddar ned en färdig box med Ubuntu 12.04 LTS. Denna box namnges till <code>precise64</code> och kan sedan återanvändas för flera virtuella maskiner. Nedladdning sker endast en gång. Rad 2 och 3 skapar en katalog där din virtuella maskin sparas. Kommandot på rad 4 skapar en så kallad <code>Vagrantfile</code> som beskriver hur just din box ska konfigureras (bland annat port forwarding). Vagrant själva skriver att:</p>
<blockquote><p>A Vagrantfile is to Vagrant as a Makefile is to Make</p></blockquote>
<p>Innehållet i vår nyskapade <code>Vagrantfile</code> ser ut ungefär så här:</p>
<pre class="brush: ruby; title: ;">
Vagrant::Config.run do |config|
  # Setup the box
  config.vm.box = &quot;precise64&quot;
end
</pre>
<p>Den katalogen som innehåller <code>Vagrantfile</code> mappas (per default) upp som en <em>shared folder</em> så din maskin och den virtuella boxen kan dela filer där på ett smidigt sätt.</p>
<p>För att sedan starta vår virtuella server används följande kommando:</p>
<pre class="brush: bash; title: ;">
$ vagrant up
</pre>
<p>Och för att koppla upp dig via SSH mot servern körs detta kommando:</p>
<pre class="brush: bash; title: ;">
$ vagrant ssh
</pre>
<p>När du kopplat upp dig via SSH kan du accessa dina delade filer i foldern <code>/vagrant</code></p>
<p>Om du gör förändringar av din virtuella box som du sedan vill återanvända kan du skapa egna paket (boxar) som du senare kan utgå från. Detta sker enkelt via kommandot:</p>
<pre class="brush: bash; title: ;">
$ vagrant package
</pre>
<p>Då skapas en box &#8211; <code>package.box</code> &#8211; som kan läggas till bland dina befintliga boxar med hjälp av
<pre class="brush: bash; title: ;">
$ vagrant box add my_box /path/to/package.box
</pre>
<p>När du känner att du är färdig med din virtuella box kan du använda följande kommando för att stänga ned maskinen (och därmed spara CPU/RAM):</p>
<pre class="brush: bash; title: ;">
$ vagrant halt
</pre>
<p>Alternativt, om du bara vill göra en suspend (det går snabbare att starta maskinen senare isf):</p>
<pre class="brush: bash; title: ;">
$ vagrant suspend
</pre>
<p>För att starta maskinen efter <code>halt</code> eller <code>suspend</code> är det bara att köra:</p>
<pre class="brush: bash; title: ;">
$ vagrant up
</pre>
<p>Om du vill ta bort din virtuella box från disken helt och hållet kör du bara kommandot:</p>
<pre class="brush: bash; title: ;">
$ vagrant destroy
</pre>
<p>Vagrant erbjuder också stöd för mer avancerade features såsom provisioning mha <a href="http://www.opscode.com/" target="_blank">Chef</a> eller <a href="https://puppetlabs.com/" target="_blank">Puppet</a>. Allt detta finns att läsa på <a href="http://docs.vagrantup.com/v1/docs/provisioners.html" target="_blank">Vagrants hemsida.</a></p>
<p>Så, superenkel hantering av virtuella boxar med hjälp av Vagrant &#8211; rekommenderas varmt!</p>
<p>Lathund:</p>
<table>
<tr>
<td><code>$ vagrant init</code></td>
<td>Skapar en <code>Vagrantfile</code></td>
</tr>
<tr>
<td><code>$ vagrant up</code></td>
<td>Startar en virtuell box</td>
</tr>
<tr>
<td><code>$ vagrant halt</code></td>
<td>Stoppar den virtuella boxen</td>
</tr>
<tr>
<td><code>$ vagrant suspend</code></td>
<td>Pausar den virtuella boxen</td>
</tr>
<tr>
<td><code>$ vagrant destroy</code></td>
<td>Stoppar och tar bort den virtuella boxen från disken</td>
</tr>
<tr>
<td><code>$ vagrant package</code></td>
<td>Skapar ett nytt box-paket baserat på en befintlig box</td>
</tr>
<tr>
<td><code>$ vagrant box add my_box /path/to/package.box</code></td>
<td>Lägger till en ny box-typ (my_box) som pekas ut via path eller HTTP</td>
</tr>
<tr>
<td><code>$ vagrant box list</code></td>
<td>Listar tillgängliga boxar för din lokala miljö. En lista över boxar nedladdningsbara boxar <a href="http://www.vagrantbox.es/" target="_blank">finns här</a>.</td>
</tr>
</table>

	Tags: <a href="http://cygni.se/taggar/chef/" title="chef" rel="tag">chef</a>, <a href="http://cygni.se/taggar/linux/" title="linux" rel="tag">linux</a>, <a href="http://cygni.se/taggar/provisioning/" title="provisioning" rel="tag">provisioning</a>, <a href="http://cygni.se/taggar/puppet/" title="puppet" rel="tag">puppet</a>, <a href="http://cygni.se/taggar/ubuntu/" title="ubuntu" rel="tag">ubuntu</a>, <a href="http://cygni.se/taggar/vagrant/" title="vagrant" rel="tag">vagrant</a>, <a href="http://cygni.se/taggar/virtual-box/" title="virtual box" rel="tag">virtual box</a><br />
]]></content:encoded>
			<wfw:commentRss>http://cygni.se/2013/02/26/vagrant-den-basta-nyheten-sedan-skivat-brod/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ny kund &#8211; hitta.se</title>
		<link>http://cygni.se/2013/02/15/ny-kund-hitta-se/</link>
		<comments>http://cygni.se/2013/02/15/ny-kund-hitta-se/#comments</comments>
		<pubDate>Fri, 15 Feb 2013 08:06:57 +0000</pubDate>
		<dc:creator>Tommy Wassgren</dc:creator>
				<category><![CDATA[Pressmeddelanden]]></category>

		<guid isPermaLink="false">http://cygni.se/?p=4761</guid>
		<description><![CDATA[Cygni har tecknat avtal med hitta.se om att leverera avancerade konsulttjänster inom systemutveckling. Prisbelönta hitta.se hjälper dagligen hundratusentals svenskar att hitta företag, personer och platser. På hitta.se finns adresser och telefonnummer till alla svenska företag och privatpersoner. Dessutom finns branschsök, kartor, vägbeskrivningar, gatubilder, satellitbilder och mycket annat som hjälper till att hitta rätt. Avtalet befäster ...]]></description>
			<content:encoded><![CDATA[<p>Cygni har tecknat avtal med <a href="http://hitta.se">hitta.se</a> om att leverera avancerade konsulttjänster inom systemutveckling. </p>
<p>Prisbelönta <a href="http://hitta.se">hitta.se</a> hjälper dagligen hundratusentals svenskar att hitta företag, personer och platser. På <a href="http://hitta.se">hitta.se</a> finns adresser och telefonnummer till alla svenska företag och privatpersoner. Dessutom finns branschsök, kartor, vägbeskrivningar, gatubilder, satellitbilder och mycket annat som hjälper till att hitta rätt.</p>
<p><img src="http://cygni.se/wp-uploads/2011/02/hitta.se_-300x300.jpg" alt="hitta.se" title="hitta.se" width="300" height="300" class="aligncenter size-medium wp-image-4759" /></p>
<p>Avtalet befäster ytterligare Cygnis ställning som en av de ledande kvalitetsleverantörerna av systemutvecklingstjänster för högt trafikerade webbplatser.</p>
Inga taggar hittades]]></content:encoded>
			<wfw:commentRss>http://cygni.se/2013/02/15/ny-kund-hitta-se/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Bättre koll på testerna med EclEmma</title>
		<link>http://cygni.se/2013/02/12/battre-koll-pa-testerna-med-eclemma/</link>
		<comments>http://cygni.se/2013/02/12/battre-koll-pa-testerna-med-eclemma/#comments</comments>
		<pubDate>Tue, 12 Feb 2013 12:09:47 +0000</pubDate>
		<dc:creator>Mikael Elm</dc:creator>
				<category><![CDATA[Stacktrace]]></category>
		<category><![CDATA[cobertura]]></category>
		<category><![CDATA[code coverage]]></category>
		<category><![CDATA[coverage tools]]></category>
		<category><![CDATA[eclemma]]></category>
		<category><![CDATA[eclipse]]></category>
		<category><![CDATA[eclipse plugins]]></category>
		<category><![CDATA[emma]]></category>
		<category><![CDATA[test]]></category>
		<category><![CDATA[test coverage]]></category>

		<guid isPermaLink="false">http://cygni.se/?p=4689</guid>
		<description><![CDATA[I den här artikeln ska jag skriva lite om EclEmma, en plugin till Eclipse för testramverket EMMA. Pluginen är inte på något sätt ny, men inte desto mindre användbar. Verktyget är till för att visa dig hur väl dina tester täcker din kod, s. k. test coverage. Även om man utvecklar enligt TDD (Test Driven ...]]></description>
			<content:encoded><![CDATA[<p>I den här artikeln ska jag skriva lite om <a href="http://www.eclemma.org/" target="_blank">EclEmma</a>, en plugin till <a href="http://www.eclipse.org/" target="_blank">Eclipse</a> för testramverket <a href="http://emma.sourceforge.net/" target="_blank">EMMA</a>. Pluginen är inte på något sätt ny, men inte desto mindre användbar. Verktyget är till för att visa dig hur väl dina tester täcker din kod, s. k. <em>test coverage</em>.</p>
<p>Även om man utvecklar enligt <em>TDD</em> (Test Driven Development) så kan det vara bra att få ett kvitto på att ens egen bild av testerna stämmer överens med verkligheten. För den som inte utvecklar testdrivet är <em>EMMA</em> en riktig ”eye-opener” för att, allt som oftast, inse att det behövs fler tester.</p>
<p>Jag fortsätter med ett exempel av Hello World-kaliber. Ladda gärna hem pluginen själv och prova samtidigt, projektets hemsida är <a href="http://www.eclemma.org/">www.eclemma.org</a> där du kan välja manuell nerladdning eller använda länken till projektets update-site, samt att det finns tillgängligt i Eclipse Marketplace.</p>
<p>Då kör vi. Testet ser ut såhär:</p>
<pre class="brush: java; title: ;">
public class EmmaTest {

 @Test
 public void testGreeter() {
    Greeter greeter = new Greeter();
    Assert.assertEquals(&quot;Hello Cygni!&quot;, greeter.greet(&quot;Cygni&quot;));
 }
}
</pre>
<p>Och klassen vi ska testa:</p>
<pre class="brush: java; title: ;">
public class Greeter {

 public String greet(String name) {
    if(name != null) {
       return &quot;Hello &quot; + name + &quot;!&quot;;
    } else {
       return &quot;Who are you?&quot;;
    }
 }

 public String greetSimple() {
    return &quot;Hello you!&quot;;
 }
}
</pre>
<p>Att testet inte täcker hela <code>Greeter</code>-klassen har du nog redan insett. Vi låter dock <em>EclEmma</em> sköta analysen.</p>
<p>När <em>EclEmma</em> är installerat dyker ytterligare ett alternativ för att exekvera upp. Sedan tidigare har vi <code>Run as</code> och <code>Debug as</code>, men nu kommer även <code>Coverage as</code> att finnas tillgängligt. Om du kör Java-perspektivet skall också en ikon för detta ha tillkommit (kör du andra perspektiv kan du behöva lägga till ikonen via ”Customize perspective”). Vi kör testet via <code>Coverage as</code>:</p>
<p><img class="alignnone size-full wp-image-4693" title="coverageAs" src="/wp-uploads/2013/01/coverageAs.png" alt="" width="507" height="200" /></p>
<p>Testet körs och vi får upp Coverage-vyn, samt att kodraderna i de klasser där det körts kod kommer att ha fått lite färg. Vi börjar med Coverage-vyn:</p>
<p><img class="alignnone size-full wp-image-4694" title="coverageView" src="http://cygni.se/wp-uploads/2013/01/coverageView.png" alt="" width="514" height="226" /></p>
<p>Som synes kan vi gå ner på olika nivåer för att tydligare se var det täcker bättre eller sämre. Efter att ha expanderat trädet fullt ut ner på metodnivå ser vi att <code>greetSimple()</code> har 0% täckning och att <code>greet()</code> behöver testas mer för att nå 100%. Metoden <code>greetSimple()</code> tog jag bara med här för att visa på hur användbart det är att kunna se test coverage på metodnivå och på så sätt tydligare identifiera svagheter, men jag låter den vara otestad för det här exemplet.</p>
<p>Om vi öppnar klassen <code>Greeter</code>, så ser ni tydligt vad som är testat och inte:</p>
<p><img class="alignnone size-full wp-image-4699" title="stopLights" src="http://cygni.se/wp-uploads/2013/01/stopLights.png" alt="" width="388" height="257" /></p>
<p>I <code>greetSimple()</code> är det inte så mycket att tala om, den är röd (<em>no coverage</em>) eftersom vi inte har något test som kör metoden. I <code>greet()</code> är raden med <code>”Who are you?”</code> röd eftersom vi inte har gjort något anrop där <code>if</code>-satsen utvärderas till <code>false</code>.</p>
<p>Den gröna raden är grön (<em>full coverage</em>) därför att det är den rad som exekverades när metoden returnerade <code>”Hello Cygni!”</code>, det väntade resultatet av vårt test.</p>
<p>Den gula raden betyder att raden är delvis täckt (<em>partial coverage</em>), en mycket användbar indikator. I det här fallet vet vi att det finns två scenarion för <code>if</code>-satsen. Antingen har argumentet <code>name</code> värdet <code>null</code>, eller så är <code>name</code> skilt från <code>null</code>. I vårt test skickade vi in <code>”Cygni”</code>, <code>name</code> var alltså skilt från <code>null</code>. <em>EclEmma</em> talar också om för oss hur många fall vi har missat om vi håller muspekaren över diamantikonen bredvid:</p>
<p><img class="alignnone size-full wp-image-4700" title="oneOutOfTwo" src="http://cygni.se/wp-uploads/2013/01/oneOutOfTwo.png" alt="" width="234" height="47" /></p>
<p>Vi skriver ytterligare ett test för att nå 100% test coverage i metoden <code>greet()</code>:</p>
<pre class="brush: java; title: ;">
@Test
 public void testGreeterNullArg() {
    Greeter greeter = new Greeter();
    Assert.assertEquals(&quot;Who are you?&quot;, greeter.greet(null));
 }
</pre>
<p>Resultatet ser nu mycket trevligare ut:</p>
<p><img class="alignnone size-full wp-image-4701" title="allGreenCode" src="http://cygni.se/wp-uploads/2013/01/allGreenCode.png" alt="" width="372" height="172" /></p>
<p><img class="alignnone size-full wp-image-4702" title="oneHundredInView" src="http://cygni.se/wp-uploads/2013/01/oneHundredInView.png" alt="" width="472" height="58" /></p>
<p>En inställning som är bra att känna till är vilken typ av counter man kör. Hur stor test coverage dina tester har kan variera stort beroende på hur man räknar. I vårt exempel kunde vi se att vi i <code>if</code>-satsen hade två stycken branches. I första testet kördes bara ett anrop där uttrycket i <code>if</code>-satsen var sant (<code>name</code> var skilt från <code>null</code>), vilket innebar att vi täckte en branch av två &#8211; branchen där <code>name</code> var <code>null</code> kördes inte. Alltså hade vi i det testet 50% <em>branch coverage</em> på den raden.</p>
<p>Men, om vi istället räknar <em>line coverage</em>, så är siffran 100% eftersom line coverage inte tar hänsyn till vilken typ av kodrad som exekveras, bara att den exekveras. Klicka på den vita nedåtriktade pilen i coverage-vyn för att ändra typ av counter:</p>
<p><img class="alignnone size-full wp-image-4703" title="counters" src="http://cygni.se/wp-uploads/2013/01/counters.png" alt="" width="229" height="314" /></p>
<p>Du kan läsa mer om de olika typerna av counters som finns på <em>EclEmma</em>&#8216;s <a href="http://www.eclemma.org/" target="_blank">hemsida</a> eller exempelvis <a href="http://en.wikipedia.org/wiki/Code_coverage" target="_blank">Wikipedia</a>.</p>
<h3>Två tips innan vi är färdiga:</h3>
<ol>
<li><em>EMMA</em> och andra liknande verktyg (<a href="http://cobertura.sourceforge.net/">Cobertura</a> till exempel) finns som plugins till exempelvis <a href="http://maven.apache.org/" target="_blank">Maven</a> och <a href="http://jenkins-ci.org/" target="_blank">Jenkins</a>, som då genererar en rapport över hur testerna gick i ett bygge. Att ha statistik och rapporter över testresultat och trender i projektet ger en trevlig överblick.</li>
<li>Om du kör ett mörkt tema för <em>Eclipse</em>, vilket inte är särskilt ovanligt, rekommenderar jag att ändra highlight-färgerna för <em>EclEmma</em>, annars är risken stor att du inte ser texten på raderna. Gå till <code>Window -&gt; Preferences -&gt; General -&gt; Editors -&gt; Text Editors -&gt; Annotations</code> och välj mörkare färger för alternativen <code>Full coverage</code>, <code>Partial coverage</code> och <code>No coverage</code>.</li>
</ol>

	Tags: <a href="http://cygni.se/taggar/cobertura/" title="cobertura" rel="tag">cobertura</a>, <a href="http://cygni.se/taggar/code-coverage/" title="code coverage" rel="tag">code coverage</a>, <a href="http://cygni.se/taggar/coverage-tools/" title="coverage tools" rel="tag">coverage tools</a>, <a href="http://cygni.se/taggar/eclemma/" title="eclemma" rel="tag">eclemma</a>, <a href="http://cygni.se/taggar/eclipse/" title="eclipse" rel="tag">eclipse</a>, <a href="http://cygni.se/taggar/eclipse-plugins/" title="eclipse plugins" rel="tag">eclipse plugins</a>, <a href="http://cygni.se/taggar/emma/" title="emma" rel="tag">emma</a>, <a href="http://cygni.se/taggar/test/" title="test" rel="tag">test</a>, <a href="http://cygni.se/taggar/test-coverage/" title="test coverage" rel="tag">test coverage</a><br />
]]></content:encoded>
			<wfw:commentRss>http://cygni.se/2013/02/12/battre-koll-pa-testerna-med-eclemma/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Dropwizard &#8211; en översikt</title>
		<link>http://cygni.se/2013/01/24/dropwizard-en-oversikt/</link>
		<comments>http://cygni.se/2013/01/24/dropwizard-en-oversikt/#comments</comments>
		<pubDate>Thu, 24 Jan 2013 08:52:08 +0000</pubDate>
		<dc:creator>Emil Breding</dc:creator>
				<category><![CDATA[Stacktrace]]></category>
		<category><![CDATA[dropwizard]]></category>
		<category><![CDATA[hibernate]]></category>
		<category><![CDATA[jackson]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[jdbi]]></category>
		<category><![CDATA[jetty]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[maven]]></category>
		<category><![CDATA[rest]]></category>

		<guid isPermaLink="false">http://cygni.se/?p=4645</guid>
		<description><![CDATA[Dropwizard är en körmiljö och ett ramverk för att enkelt utveckla små enheter i form av RESTful webtjänster. En stack bestående av bl.a Jetty, Jersey, Jackson, slf4j, JDBI och en föreslagen projektstruktur gör det väldigt lätt att komma igång! Begrepp Dropwizard har idenitifierat följande aktörer och entiteter i sin stack: Configuration Konfigurationen är till för ...]]></description>
			<content:encoded><![CDATA[<p><a href="http://dropwizard.codahale.com/" target="_blank">Dropwizard</a> är en körmiljö och ett ramverk för att enkelt utveckla små enheter i form av RESTful webtjänster. En stack bestående av bl.a <a href="http://www.eclipse.org/jetty/" target="_blank">Jetty</a>, <a href="http://jersey.java.net/" target="_blank">Jersey</a>, <a href="http://jackson.codehaus.org/">Jackson</a>, <a href="http://www.slf4j.org/" target="_blank">slf4j</a>, <a href="http://jdbi.org/" target="_blank">JDBI</a> och en föreslagen projektstruktur gör det väldigt lätt att komma igång!</p>
<h2>Begrepp</h2>
<p>Dropwizard har idenitifierat följande aktörer och entiteter i sin stack:</p>
<h3>Configuration</h3>
<p>Konfigurationen är till för att hålla miljöspecifika aspekter av tjänsten. Det kan t.ex vara databaskonfiguration eller nödvändiga sökvägar och url:ar. Konfigurationen skrivs i formatet <a href="http://www.yaml.org/" target="_blank">YAML</a>. Genom JSON-annotationer i din klass läser DropWizard automatiskt upp konfigurationen och populerar din konfigurationsklass.</p>
<h3>Representation</h3>
<p>Detta är de objekt som ska skickas mellan klient och server och är i sin enklaste form vanliga <a href="http://en.wikipedia.org/wiki/Plain_Old_Java_Object" target="_blank">POJOs</a>. Jackson används för serialisering och deserialisering. DropWizard rekommenderar att dessa objekt är <a href="http://en.wikipedia.org/wiki/Immutable" target="_blank">immutable</a>.  </p>
<h3>Resource</h3>
<p>Det är resurserna som exponerar REST API:et. Varje resurs och metod har en URI och klassen annoteras med <code>javax.ws.rs.*</code>-typerna för att definiera sökvägar, innehållstyper och metod. DropWizard använder Jersey-implementationen.</p>
<h3>Health check</h3>
<p>Hälsokontroller är små kodsnuttar som testar att tjänsten är uppe och att allt är som det ska. Status för hälsokontrollerna kan inspekteras via en speciell admin-url. DropWizard skapar automatiskt några hälsokontroller, bl.a en som kontrollerar att databasanslutningen är korrekt (om man har en i sitt projekt). </p>
<h3>Service</h3>
<p>Servicen ansvarar för att dra igång tjänsten med en konfiguration och med resurserna initialiserade med eventuella beroenden. Detta sker genom en vanlig main-metod(<code>public static void main(String[] args) { ... } </code>).</p>
<p>I vårt exempelprojekt ser serviceklassen ut så här:</p>
<pre class="brush: java; title: ;">
public class MainService extends Service&lt;MainConfiguration&gt; {
    public static void main(String[] args) throws Exception {
        new MainService().run(args);
    }

    @Override
    public void initialize(Bootstrap&lt;MainConfiguration&gt; mainConfigurationBootstrap) {
        mainConfigurationBootstrap.addBundle(new AssetsBundle(&quot;/assets/&quot;, &quot;/&quot;));
    }

    @Override
    public void run(MainConfiguration config, Environment environment) throws Exception {

        DBI dbi = initDatabaseConnection(config, environment);

        final EventsRepository eventsRepository = dbi.onDemand(EventsRepository.class);
        final ExpensesRepository expensesRepository = dbi.onDemand(ExpensesRepository.class);

        environment.addResource(new EventsResource(eventsRepository));
        environment.addResource(new ExpensesResource(expensesRepository));
    }

    private DBI initDatabaseConnection(MainConfiguration config, Environment environment) throws ClassNotFoundException {

        final DBIFactory factory = new DBIFactory();
        final DBI jdbi = factory.build(environment, config.getDatabaseConfiguration(), &quot;db&quot;);

        createTables(jdbi);

        return jdbi;
    }

    private void createTables(DBI dbi) {

        Handle handle = dbi.open();

        handle.execute(EventsRepository.CREATE_TABLE_STATEMENT);

        handle.execute(ExpensesRepository.CREATE_TABLE_STATEMENT);

        handle.close();
    }
}
</pre>
<p>För dig som är van vid Spring eller något annat DI-ramverk kan det kännas lite konstigt att själv behöva instansiera t.ex resursklasserna (och skjuta in Repository-implementationerna). Eftersom tanken med en DropWizard tjänst är att ansvara för en relativt liten delmängd av ett system så tycker jag inte att detta är särskilt besvärande. </p>
<p>Resurser, dvs REST-tjänsterna, aktiveras genom att lägga till dem till instansen av <code>Environment</code>. Se rad 19-20 ovan. </p>
<h2>Komma igång</h2>
<p>Checka ut exempelprojektet och bygg:</p>
<pre class="brush: bash; title: ;">
git clone git://github.com/cygni-stacktrace/dropwizard-sample.git
cd dropwizard-sample
mvn package
</pre>
<p>Starta tjänsten med kommandot <code>server</code> och filnamnet för konfigurationen:</p>
<pre class="brush: bash; title: ;">
java -jar target/expenses-1.0-SNAPSHOT.jar server development.yml
</pre>
<p>Rest-API:et ligger under kontextet <code>/api</code>, en <code>GET</code>-operation för att lista events finns t.ex här: <a href="http://localhost:8080/api/event">http://localhost:8080/api/event</a></p>
<p>Admingränssnittet med länk till Health check finns här: <a href="http://localhost:8081">http://localhost:8081</a></p>
<h2>Testning</h2>
<p>Testning av resurser är en enkel historia med DropWizard. Genom att låta din testklass uttöka <code>ResourceTest</code> kan du lägga till resursen under test så här:</p>
<pre class="brush: java; title: ;">
    @Override
    protected void setUpResources() throws Exception {
        eventsRepository = mock(EventsRepository.class);
        addResource(new EventsResource(eventsRepository));
    }
</pre>
<p>En testmetod:</p>
<pre class="brush: java; title: ;">
    @Test
    public void shouldShowOneEvent() throws IOException {
        //given
        Event event = new Event(1, &quot;An event&quot;, null);

        given(eventsRepository.findById(event.getId())).willReturn(event);

        //when
        Event result = client().resource(&quot;/event/1&quot;).get(Event.class);

        //then
        assertThat(&quot;event should be returned&quot;, result, is(event));
    }
</pre>
<p>Dropwizard drar igång Jersey och invokerar anropen i testet via en HTTP-klient så hela request/response kedjan går över JSON och HTTP. Detta är bra för då testas även att Jackson kan serialisera och deserialisera dina POJO:s. </p>
<h2>Databasstöd</h2>
<p>Dropwizard har moduler med stöd för <a href="http://www.hibernate.org/" target="_blank">Hibernate</a> och <a href="http://jdbi.org/">JDBI</a>. I exempelprojektet används JDBI.</p>
<h2>Slutsats</h2>
<p>Det är väldigt lätt att komma igång med Dropwizard och jag tycker att de valt en vettig stack med hjälpbibliotek. Den föreslagna projektstrukturen och användandet av <a href="http://maven.apache.org/" target="_blank">Maven</a> gör att det fungerar bra i alla stora IDE:er. Att inkludera Health checks direkt från början känns väldigt bra och det är befriande att ha en app som kan köras helt stand-alone (d.v.s den behöver inte deployas i en webcontainer). Jag tycker också att de tänkt till bra kring testningen.</p>
<p>Om man planerar att börja använda Dropwizard i ett större sammanhang behövs dock en strategi för hur tjänster ska kunna versionshanteras samt upptäckas. </p>
<h2>Vidare läsning</h2>
<p><a href="http://dropwizard.codahale.com" target="_blank">http://dropwizard.codahale.com</a></p>

	Tags: <a href="http://cygni.se/taggar/dropwizard/" title="dropwizard" rel="tag">dropwizard</a>, <a href="http://cygni.se/taggar/hibernate/" title="hibernate" rel="tag">hibernate</a>, <a href="http://cygni.se/taggar/jackson/" title="jackson" rel="tag">jackson</a>, <a href="http://cygni.se/taggar/java/" title="java" rel="tag">java</a>, <a href="http://cygni.se/taggar/jdbi/" title="jdbi" rel="tag">jdbi</a>, <a href="http://cygni.se/taggar/jetty/" title="jetty" rel="tag">jetty</a>, <a href="http://cygni.se/taggar/json/" title="json" rel="tag">json</a>, <a href="http://cygni.se/taggar/maven/" title="maven" rel="tag">maven</a>, <a href="http://cygni.se/taggar/rest/" title="rest" rel="tag">rest</a><br />
]]></content:encoded>
			<wfw:commentRss>http://cygni.se/2013/01/24/dropwizard-en-oversikt/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Strategier för felhantering</title>
		<link>http://cygni.se/2013/01/08/bort-med-printstacktrace/</link>
		<comments>http://cygni.se/2013/01/08/bort-med-printstacktrace/#comments</comments>
		<pubDate>Tue, 08 Jan 2013 08:18:18 +0000</pubDate>
		<dc:creator>Alexander Torstling</dc:creator>
				<category><![CDATA[Stacktrace]]></category>

		<guid isPermaLink="false">http://cygni.se/?p=4562</guid>
		<description><![CDATA[Detta inlägg argumenterar för att mönstret try { ... } catch(SomeCheckedException e) { e.printStackTrace(); } är ett anti-pattern och definitivt ej bör rekommenderas som ett generellt felhanteringsmönster. Vad är det då för fel på denna konstruktion? Jo, problemet är att den bryter mot en bra grundprincip inom felhantering, &#8221;fail fast&#8221;. Principen &#8221;fail fast&#8221; bygger på ...]]></description>
			<content:encoded><![CDATA[<p>Detta inlägg argumenterar för att mönstret</p>
<pre class="brush: java; title: ;">
try {
   ...
} catch(SomeCheckedException e) {
   e.printStackTrace();
}
</pre>
<p>är ett <a href="http://en.wikipedia.org/wiki/Anti-pattern">anti-pattern</a> och definitivt ej bör rekommenderas som ett generellt felhanteringsmönster.</p>
<p>Vad är det då för fel på denna konstruktion? Jo, problemet är att den bryter mot en bra grundprincip inom felhantering, <a href="http://c2.com/cgi/wiki?FailFast">&#8221;fail fast&#8221;</a>.</p>
<p>Principen &#8221;fail fast&#8221; bygger på att man avbryter programmet så fort ett fel uppstår. Huvudorsaken till att avbryta tidigt är att undvika följdfel. Samtidigt så undviks maskeringar av fel, vilket gör det lättare att diagnostisera och korrigera brister. Följs denna teknik och kombineras med vältäckande tester kan man eliminera många buggar och uppnå en hög genomgående kvalité i sin produkt.</p>
<p>När denna princip är på plats kan man sedan välja göra programmet mer flexibelt genom att selektivt lägga till felhanteringsstrategier.</p>
<p>Detta görs ofta genom att lägga till alternativa strategier för specifika felfall. Detta benämns ofta som <a href="http://en.wikipedia.org/w/index.php?title=Graceful_degradation">&#8221;graceful degradation&#8221;</a>. Som ett exempel kan man tänka sig en e-handel som köar upp ordrar då kontakten med banken faller bort. Har man en sådan &#8221;plan b&#8221; så har man täckt upp för det specifika felfallet att kontakten med banken faller bort. Ett annat exempel är att falla tillbaka till en enklare webbsida då en plug-in saknas eller en äldre browser används.</p>
<p>För oplanerade fel, sådana där man ofta inte har någon uttänkt strategi, är det bästa man kan göra att återställa systemet till ett känt säkert läge. Ett allmängiltigt sätt att skicka ett program till ett känt läge är att avbryta det. Är ditt system inte livsuppehållande eller på annat sätt kritiskt är detta läge även säkert. Så en bra grundstrategi är att avbryta programmet när ett okänt fel uppstår.</p>
<p>Tillbaka då till strategin att anropa <a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html#printStackTrace()">printStackTrace()</a>. Denna felhanterare gör två saker:</p>
<ol>
<li>Skriver ut en felrapport</li>
<li>Fortsätter exekveringen av programmet</li>
</ol>
<p>Det är i huvudsak punkt två som bryter mot &#8221;fail fast&#8221;. Om man har en &#8221;graceful degradation&#8221; eller &#8221;fail safe&#8221;-strategi på plats så bör man när man fångat felet anropa &#8221;plan b&#8221;, inte bara fortsätta. Är felet istället oplanerat så bör man så snart som möjligt försätta programmet i ett säkert läge och skriva ut en felrapport.</p>
<p>I <code>printStackTrace</code>-fallet försätts inte programmet i ett säkert läge. Programmet tillåts fortsätta att exekvera och kommer förmodligen trigga följdfel. I avseendet följdfel är därmed denna form av felhantering inte bättre än det välkända anti-patternet att svälja felet helt:</p>
<pre class="brush: java; title: ;">
try {
   ...
} catch(SomeCheckedException e) {
  //dålig taktik
  //no-op
}
</pre>
<p>. Inte heller taktiken att skriva ut felrapporten på en logg undslipper detta argument:</p>
<pre class="brush: java; title: ;">
try {
   ...
} catch(SomeCheckedException e) {
   //dålig taktik
  logger.error(e);
}
</pre>
<p>. En tredje teknik som förekommer är <a href="http://today.java.net/article/2006/04/04/exception-handling-antipatterns#logAndThrow">log and throw</a>:</p>
<pre class="brush: java; title: ;">
try {
   ...
} catch(SomeCheckedException e) {
   //dålig taktik
  logger.error(e);
  throw new RuntimeException(e);
}
</pre>
<p>. Att kasta vidare räcker gott och väl, annars riskerar man flera utskrifter för samma fel.</p>
<p>Man vill alltså försätta programmet i ett säkert läge. Dessutom vill man ha en felutskrift utan duplikat. Vän av ordning frågar sig såklart hur man åstadkommer detta på ett enkelt sätt. Svaret är enkelt: kasta exceptionet vidare:</p>
<pre class="brush: java; title: ;">
try {
   ...
} catch(SomeCheckedException e) {
   //bra taktik
  throw new RuntimeException(e);
}
</pre>
<p>.<br />
Vad åstadkommer en sådan konstruktion? Jo, felet kommer att propageras uppåt i stacken tills det når trådens topp, där ett stacktrace skrivs ut innan tråden avslutas. Detta beteende är inbyggt i Java. Har du en enkel applikation med endast en non-deamon-tråd så kommer programmet även att avslutas i samband med att ditt fel skrivs ut. Fallet multitrådade program är mer invecklat och tas inte upp i detta inlägg.</p>
<p>För att på ett enkelt sätt kunna följa dessa principer rekommenderar jag att du ändrar mallen för &#8221;surround with try-catch&#8221; i ditt IDE så att den kastar checked exceptions vidare, istället för att anropa <code>printStackTrace()</code>.</p>
<p>I Eclipse kan du göra detta i inställningen <code>Java &gt; Code Style &gt; Code Templates</code> och ändra från default</p>
<pre class="brush: java; title: ;">
// ${todo} Auto-generated catch block
${exception_var}.printStackTrace();
</pre>
<p>till</p>
<pre class="brush: java; title: ;">
throw new RuntimeException(${exception_var});
</pre>
<p>På detta sätt får du den bra taktiken per default när du gör &#8221;quick fix&#8221;!</p>
<p><a href="http://cygni.se/2013/01/08/bort-med-printstacktrace/quick-fix/" rel="attachment wp-att-4633"><img src="http://cygni.se/wp-uploads/2012/12/quick-fix-300x221.png" alt="quick fix" title="quick fix" width="300" height="221" class="alignnone size-medium wp-image-4633" /></a></p>
Inga taggar hittades]]></content:encoded>
			<wfw:commentRss>http://cygni.se/2013/01/08/bort-med-printstacktrace/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Integrationstester &#8211; TestLink och JUnit</title>
		<link>http://cygni.se/2013/01/07/integrationstester-testlink-och-junit/</link>
		<comments>http://cygni.se/2013/01/07/integrationstester-testlink-och-junit/#comments</comments>
		<pubDate>Mon, 07 Jan 2013 10:36:54 +0000</pubDate>
		<dc:creator>Lars Svadängs</dc:creator>
				<category><![CDATA[Stacktrace]]></category>
		<category><![CDATA[eclipse]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[junit]]></category>
		<category><![CDATA[parallell]]></category>
		<category><![CDATA[parametriserad]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[rpc]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[spring]]></category>
		<category><![CDATA[teknik]]></category>
		<category><![CDATA[test]]></category>
		<category><![CDATA[testlink]]></category>
		<category><![CDATA[xml]]></category>

		<guid isPermaLink="false">http://cygni.se/?p=4523</guid>
		<description><![CDATA[Detta är den sista delen av tre i en artikelserie om automatiserade integrationstester. Den första delen ger en kort beskrivning av syftet med integrationstester och de utmaningar som ofta uppstår vid testning. Där visar jag också hur man kan parallellisera tester i JUnit för att minska exekveringstiden. Del två visar på användningen av parametriserade tester ...]]></description>
			<content:encoded><![CDATA[<p>Detta är den sista delen av tre i en artikelserie om automatiserade integrationstester. <a href="/2012/12/17/automatiserade-integrationstester/">Den första delen</a> ger en kort beskrivning av syftet med integrationstester och de utmaningar som ofta uppstår vid testning. Där visar jag också hur man kan parallellisera tester i JUnit för att minska exekveringstiden.</p>
<p><a href="/2012/12/20/parametriserade-integrationstester/">Del två visar på användningen av parametriserade tester</a> och hur man på ett bra sätt kan använda extern testdata (testfixtures).</p>
<p>Denna sista del visar hur man kan koppla ihop sina testsviter med open source-verktyget <a href="http://www.teamst.org/" target="_blank">TestLink</a> för att få fram trevliga rapporter.<span id="more-4523"></span></p>
<h2>Koppling mot TestLink</h2>
<p>TestLink är ett open source-verktyg där du kan samla ett projekts krav och testfall. Testfallen kan sedan grupperas i olika testplaner inför exempelvis en ny release.</p>
<p>TestLink har ett  XML/RPC-gränssnitt som kan användas för att hämta information om testprojekt, testplaner, testresult mm, men som även kan användas för att uppdatera TestLink med resultatet från en testkörning. Implementationer av detta gränssnitt finns i flera språk såsom Ruby, Python och Java. En javaimplementation &#8211; TestLink Java API &#8211; finns <a href="http://testlinkjavaapi.sourceforge.net/" target="_blank">här</a>.</p>
<p>Utvecklarna av TestLink Java API har som ambition är att vara synkade med den senaste version av TestLinks XML/RPC-gränssnitt. Senaste version när detta skrivs är <code>1.9.4-0</code>. För att använda TestLink Java API måste du lägga till följande beroende i din Maven-POM.</p>
<pre class="brush: xml; title: ;">
&lt;dependency&gt;
	&lt;groupId&gt;br.eti.kinoshita&lt;/groupId&gt;
	&lt;artifactId&gt;testlink-java-api&lt;/artifactId&gt;
	&lt;version&gt;1.9.4-0&lt;/version&gt;
&lt;/dependency&gt;
</pre>
<p>Följande hjälpklass implementerar stöd för att hämta projekt, testplan mm från TestLink samt även uppdatera ett exekverat testfall med resultatet.</p>
<pre class="brush: java; title: ;">
package se.cygni.testlink;

import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import br.eti.kinoshita.testlinkjavaapi.TestLinkAPI;
import br.eti.kinoshita.testlinkjavaapi.constants.ExecutionStatus;
import br.eti.kinoshita.testlinkjavaapi.model.Build;
import br.eti.kinoshita.testlinkjavaapi.model.Platform;
import br.eti.kinoshita.testlinkjavaapi.model.TestCase;
import br.eti.kinoshita.testlinkjavaapi.model.TestPlan;
import br.eti.kinoshita.testlinkjavaapi.model.TestProject;

public class TestLinkAPIHelper {

 private static TestLinkAPIHelper INSTANCE = new TestLinkAPIHelper();
 private TestLinkAPI m_api;

 private TestLinkAPIHelper() {
   try {
     URL url = new URL(&quot;/lib/api/xmlrpc.php&quot;);
     m_api = new TestLinkAPI(url, &quot;&quot;);
   } catch (Exception e) {
     e.printStackTrace(System.out);
     throw new RuntimeException(&quot;Unable to create TestLinkApi&quot;);
   }
 }

 public static TestLinkAPIHelper getInstance() {
   return INSTANCE;
 }

 public TestProject getProject(String projectName) {
   return m_api.getTestProjectByName(projectName);
 }

 public TestPlan getTestPlan(TestProject project, String testPlanName) {
   return m_api.getTestPlanByName(testPlanName, project.getName());
 }

 public Platform getPlatform(TestPlan testPlan, String platformName) {
   Platform[] platforms = m_api.getTestPlanPlatforms(testPlan.getId());
   for (Platform platform: platforms) {
     if (platformName.equals(platform.getName())) {
       return platform;
     }
   }
   return null;
 }

 public Build getBuild(TestPlan plan, String buildName) {
   Build[] builds = m_api.getBuildsForTestPlan(plan.getId());
   for (Build build: builds) {
     if (buildName.equals(build.getName())) {
       return build;
     }
   }
   return null;
 }

 public TestCase[] getTestCases(TestPlan plan, Build build) {
   return m_api.getTestCasesForTestPlan(plan.getId(), null, build.getId(), null, null, null, null, null, null, false, null);
 }

 public void reportResult(TestPlan plan,Build build, Platform platform,TestCase testCase, ExecutionStatus status, String notes) {
   System.out.println(String.format(&quot;[TestLink] Reporting status %s for test case SP-%s&quot;,status.name(),testCase.getFullExternalId()));
   m_api.reportTCResult(testCase.getId(), null, plan.getId(), status, build.getId(), null, notes, false, null, platform.getId(), null, null, false);
 }

 public TestCase addTestCaseToTestPlan(TestProject project, TestPlan plan, String fullExternalId,Platform platform) {
   TestCase testCase = getTestCase(fullExternalId);
   m_api.addTestCaseToTestPlan(project.getId(), plan.getId(), testCase.getId(), testCase.getVersion(), platform.getId(),null , null);
   return testCase;
 }

 public TestCase getTestCase(String fullExternalId) {
   return m_api.getTestCaseByExternalId(fullExternalId, null);
 }

}
</pre>
<p>Andra argumentet i konstruktionen av TestLinkAPI på rad 23 skall vara din developer-key, ett hexadecimalt tal som kan genereras när du skapar den testanvändare som skall &#8221;köra&#8221; de automatiska integrationstesterna. Hur du gör för att skapa denna nyckel finns beskrivet <a href="http://www.teamst.org/forum/viewtopic.php?f=10&#038;t=1102" target="_blank">här</a>.</p>
<p>Nu är vi klara att för att uppdatera TestLink med resultatet från våra testkörningar, men hur ser vi till att vår hjälpklass anropas?</p>
<p>Ett antal saker krävs:</p>
<ol>
<li>Vi måste bli notifierade när ett testfall har körts klarts med uppgift om i fall testen gick bra eller inte</li>
<li>Vi måste när detta sker kunna anropa vår hjälpklass med korrekt TestLink-projekt, testplan, testfallsid mm</li>
</ol>
<p>En testrunner exekverar ett enskilt testfall genom ett anrop till metoden <code>runChild(final FrameworkMethod method, RunNotifier notifier)</code> där instansen av <code>RunNotifier</code> blir notifierad varje gång ett testfall har körts klart. Notifiern ansvarar för att uppdatera alla som har registrerat sig som lyssnare på testfallskörningar. Detta kan vi utnyttja för att lösa punkt 1 ovan genom att överrida <code>runChild</code> i en egen testrunner, och där registrera en egen lyssnare . Denna lyssnare skall när testfallet har kört klart rapportera resultatet.</p>
<p>Låt oss först definiera ett interface för de klasser som skall ansvara för att rapportera testresult till exempelvis ett externt system såsom TestLink:</p>
<pre class="brush: java; title: ;">

package se.cygni.testlink;

import org.junit.runner.Description;
import org.junit.runner.notification.Failure;

public interface TestReporter {

 void reportTestFailure(Failure failure);
 void reportTestSuccess(Description description);
 boolean testShouldBeRun(Description description);

}
</pre>
<p>Nu till vår testrunner som överrider runChild:</p>
<pre class="brush: java; title: ;">

package se.cygni.testlink;

import org.junit.Ignore;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.FrameworkMethod;

import se.cygni.fibonaccitest.ConcurrentJUnitRunner;

/**
 *
 * Abstract test runner with support for reporting the test results
 * to an external target, i.e. a test management tool such as TestLink.
 *
 */
public abstract class AbstractReportingTestRunner extends ConcurrentJUnitRunner {

 public AbstractReportingTestRunner(Class&lt;?&gt; klass) throws Throwable {
   super(klass);
 }

 @Override
 protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
   Description description = describeChild(method);
   if (method.getAnnotation(Ignore.class) != null) {
     notifier.fireTestIgnored(description);
   } else {
     if (testShouldBeRun(description)) {
       RunListener listener = new ResultListener(description);
       notifier.addListener(listener);
       try {
         runLeaf(methodBlock(method), description, notifier);
       } catch (Exception e) {
         handleTestFailure(new Failure(description, e));
       } finally {
         notifier.removeListener(listener);
       }
     }
   }
 }

 protected void handleTestFailure(Failure failure) {
   getTestReporter().reportTestFailure(failure);
 }

 protected void handleTestSuccess(Description description) {
   getTestReporter().reportTestSuccess(description);
 }

 protected boolean shouldTestBeRun(Description description) {
   return getTestReporter().shouldTestBeRun(description);
 }

 protected abstract TestReporter getTestReporter();

 /**
   *
   * RunListener, listening for the execution result of
   * a test case.
   *
   */
 private class ResultListener extends RunListener {

 private boolean m_failure = false;
 private Description m_description;

 public ResultListener(Description description) {
 m_description = description;
 }

 @Override
 public void testFailure(Failure failure) throws Exception {
 if (isForMe(failure.getDescription())) {
 handleTestFailure(failure);
 m_failure = true;
 }
 }

 @Override
 public void testFinished(Description description) throws Exception {
 if (isForMe(description)) {
 if (!m_failure) {
 handleTestSuccess(m_description);
 }
 }
 }

 @Override
 public void testAssumptionFailure(Failure failure) {
 if (isForMe(failure.getDescription())) {
 handleTestFailure(failure);
 m_failure = true;
 }
 }

 private boolean isForMe(Description description) {
 return m_description.getDisplayName().equals(description.getDisplayName());
 }
 }
}
</pre>
<p>Ovanstående är en abstrakt klass där konkreta subklasser är tänkt att implementera <code>getTestReporter</code> och svara med en egen test reporter.<br />
Ett (trivialt) exempel som använder sig av en implementation av <code>TestReporter</code> som loggar till konsolen:</p>
<pre class="brush: java; title: ;">
public LogToConsoleTestRunner extends AbstractReportingTestRunner {
  public LogToConsoleTestRunner(Class&lt;?&gt; klass) {
    super(klass);
  }
  @Override
  protected TestReporter getTestReporter() {
    return new TestReporter() {

     @Override
     public void reportTestFailure(Failure failure) {
       Description description = failure.getDescription();
       System.out.println(String.format(&quot;%s FAILED. Reason %s&quot;, description.getDisplayName(),failure.getMessage()));
    }

    @Override
      public void reportTestSuccess(Description description) {
      System.out.println(String.format(&quot;%s PASSED.&quot;, description.getDisplayName()));
    }

    @Override
    public boolean testShouldBeRun(Description description) {
      return true;
    }

  };
 }
}
</pre>
<p>Om vi använder <code>LogToConsoleTestRunner</code> för att exekvera följande testklass som testar en fiktiv Calculator (observera felet i test_subtraction).</p>
<pre class="brush: java; title: ;">
 package se.cygni.testreport;

 import static org.junit.Assert.*;

 import org.junit.Test;
 import org.junit.runner.RunWith;

 @RunWith(LogToConsoleTestRunner.class)
 public class CalculatorTest {

   @Test
   public void test_addition() {
     assertEquals(5,Calculator.add(2,3));
   }

   @Test
   public void test_subtraction() {
    assertEquals(5,Calculator.subtract(8,2));
   }

}
</pre>
<p>så kommer följande att loggas i konsolen</p>
<pre class="brush: plain; title: ;">
test_subtraction(se.cygni.testreport.CalculatorTest) FAILED. Reason expected:&lt;5&gt; but was:&lt;6&gt;
test_addition(se.cygni.testreport.CalculatorTest) PASSED.
</pre>
<p>För att slutligen knyta ihop exekveringen av vårt testfall med en uppdatering av TestLink behövs ytterligare några byggstenar.</p>
<p>Först en implementation av <code>TestReporter</code> som använder <code>TestLinkAPIHelper</code> för att uppdatera TestLink:</p>
<pre class="brush: java; title: ;">

package se.cygni.testlink;

import java.util.HashMap;
import java.util.Map;

import org.junit.runner.Description;
import org.junit.runner.notification.Failure;

import se.cygni.testreport.TestReporter;
import br.eti.kinoshita.testlinkjavaapi.constants.ExecutionStatus;
import br.eti.kinoshita.testlinkjavaapi.model.Build;
import br.eti.kinoshita.testlinkjavaapi.model.Platform;
import br.eti.kinoshita.testlinkjavaapi.model.TestCase;
import br.eti.kinoshita.testlinkjavaapi.model.TestPlan;
import br.eti.kinoshita.testlinkjavaapi.model.TestProject;

/**
 *
 * Implementation of {@link TestReporter}, responsible for reporting the
 * test result to the test plan in TestLink as the test cases are executed.
 *
 * There is only one instance of this class per test execution and
 * it's configured with project, test plan, platform and build, read
 * from an external configuration.
 *
 *
 */
public class TestLinkReporter implements TestReporter {

 // In TestLink it is possible to define a prefix that
 // will be added to all test case ids.
 // getFullExternalId will return id w/o this prefix though.
 private static String TEST_ID_PREFIX = &quot;&quot;;

 private TestLinkAPIHelper m_helper;
 private TestProject m_project;
 private TestPlan m_plan;
 private Platform m_platform;
 private Build m_build;
 private Map m_testCases = new HashMap();

 public TestLinkReporter(String project, String testPlan, String platform, String build) {
 System.out.println(String.format(&quot;Running tests updating project %s, test plan %s, platform%s and build %s&quot;,project,testPlan,platform,build));
 m_helper = TestLinkAPIHelper.getInstance();
 init(project, testPlan, platform, build);
 }

 private void init(String project, String testPlan, String platform, String build) {
 m_project = m_helper.getProject(project);
 m_plan = m_helper.getTestPlan(m_project, testPlan);
 m_platform = m_helper.getPlatform(m_plan, platform);
 m_build = m_helper.getBuild(m_plan, build);
 TestCase[] testCases = m_helper.getTestCases(m_plan, m_build);
 for (TestCase testCase : testCases) {
 m_testCases.put(TEST_ID_PREFIX + testCase.getFullExternalId(), testCase);
 }
 }

 public void reportTestFailure(Failure failure) {
 TestId[] testIds = getTestIds(failure.getDescription());
 for (TestId testId : testIds) {
 reportTestResult(testId,new Failed(testId,failure));
 }
 }

 public void reportTestSuccess(Description description) {
 TestId[] testIds = getTestIds(description);
 for (TestId testId : testIds) {
 reportTestResult(testId,new Passed(testId,description));
 }
 }

 @Override
 public boolean testShouldBeRun(Description description) {
 return getTestLinkAnnotation(description) != null;
 }

 private TestLink getTestLinkAnnotation(Description description) {
 return description.getAnnotation(TestLink.class);
 }

 private TestId[] getTestIds(Description description) {
 TestLink testLink = getTestLinkAnnotation(description);
 if (testLink != null) {
 String[] ids = testLink.id().split(&quot;,&quot;);
 TestId[] testIds = new TestId[ids.length];
 for (int i = 0; i &lt; ids.length ; i++) {
 testIds[i] = new TestId(ids[i].trim());
 }
 return testIds;
 } else {
 return new TestId[0];
 }
 }

 private void reportTestResult(TestId testId,TestResult testResult) {
 if (testId == null) {
 System.out.println(String.format(&quot;Invalid test id format'%s', unable to update TestLink.&quot;, testId));
 return;
 }
 TestCase testCase = getOrAddTestCase(testId);
 if (testCase != null) {
 m_helper.reportResult(m_plan, m_build, m_platform, testCase, testResult.getExecutionStatus(), testResult.getNotes());
 } else {
 System.out.println(String.format(&quot;Test case '%s' is not in test plan and not able to add it!&quot;, testId));
 }
 }
 private TestCase getOrAddTestCase(TestId testId) {
 TestCase testCase = getTestCase(testId.id);
 if (testCase == null) {
 System.out.println(String.format(&quot;Test case '%s' is not in test plan, will be added.&quot;, testId.id));
 testCase = m_helper.addTestCaseToTestPlan(m_project, m_plan, testId.id, m_platform);
 testCase.setFullExternalId(testId.id);
 m_testCases.put(testId.id, testCase);
 }
 return testCase;
 }

 private TestCase getTestCase(String testId) {
 return m_testCases.get(testId);
 }

 /**
   *
   * Utility class holding the internal test id,
   * i.e. the actual test case id, such as 'SP-472'
   *
   *
   */
 private static class TestId {
 public String id;

 public TestId(String id) {
 this.id = id;
 }

 public String toString() {
 return id;
 }
 }

 /**
   *
   * Internal classes representing the different test results
   * for a test case execution.
   *
 */
 private static abstract class TestResult {
 public TestId testId;
 public String testDescription;

 protected TestResult(TestId testId, Description description) {
 this.testId = testId;
 testDescription = getTestDescription(description);
 }

 public abstract ExecutionStatus getExecutionStatus();
 public abstract String getNotes();
 public abstract boolean passed();
 public abstract boolean failed();
 public abstract String getTestLinkNotes();
 public String toString() {
 return testId.toString() + &quot; &quot; + getExecutionStatus().name();
 }

 private String getTestDescription(Description description) {
 TestLink testLink = description != null ? description.getAnnotation(TestLink.class) : null;
 return testLink != null ? testLink.desc() : &quot;No description&quot;;
 }

 }

 private static class Passed extends TestResult {

 Passed(TestId testId, Description description) {
 super(testId, description);
 }

 @Override
 public ExecutionStatus getExecutionStatus() {
 return ExecutionStatus.PASSED;
 }

 @Override
 public String getNotes() {
 return null;
 }

 @Override
 public boolean passed() {
 return true;
 }

 @Override
 public boolean failed() {
 return false;
 }

 @Override
 public String getTestLinkNotes() {
 StringBuilder sb = new StringBuilder(testId.id);
 sb.append(&quot; '&quot;).append(testDescription).append(&quot;' PASSED.\n&quot;);
 return sb.toString();
 }

 }

 private static class Failed extends TestResult {
 public String reason;

 Failed(TestId testId, Failure failure) {
 super(testId, failure.getDescription());
 this.reason = failure.getMessage();
 }

 @Override
 public ExecutionStatus getExecutionStatus() {
 return ExecutionStatus.FAILED;
 }

 @Override
 public String getNotes() {
 return reason;
 }

 @Override
 public boolean passed() {
 return false;
 }

 @Override
 public boolean failed() {
 return true;
 }

 @Override
 public String getTestLinkNotes() {
 StringBuilder sb = new StringBuilder(testId.id);
 sb.append(&quot; '&quot;).append(testDescription).append(&quot;' FAILED.\n&quot;);
 sb.append(reason);
 return sb.toString();
 }
 }
}
</pre>
<p>Det behövs också en testrunner som använder <code>TestLinkReporter</code></p>
<pre class="brush: java; title: ;">
package se.cygni.testlink;

import java.util.LinkedList;
import java.util.List;

import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.notification.Failure;
import org.junit.runners.Suite;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;

import se.cygni.testreport.AbstractReportingTestRunner;
import se.cygni.testreport.TestReporter;

public class TestLinkSuite extends Suite {

  private static TestReporter s_testReporter = new TestReporter() {

    @Override
    public void reportTestFailure(Failure failure) {}

    @Override
    public void reportTestSuccess(Description description) {}

    @Override
    public boolean testShouldBeRun(Description description) {
      return true;
    }
  };

  public static void setTestLinkReporter(TestLinkReporter testLinkReporter) {
    s_testReporter = testReporter;
  }

  public TestLinkSuite(Class&lt;?&gt; klass, RunnerBuilder builder) throws Throwable {
    super(klass, getRunners(getAnnotatedClasses(klass)));
    System.out.println(String.format(&quot;TestLinkSuite created for running test suite %s&quot;,klass.getSimpleName()));
    try {
      filter(new TestLinkFilter());
    } catch (NoTestsRemainException e) {
      System.out.println(&quot;No TestLink tests found in &quot; + klass.getSimpleName());
    }
  }

  private static Class&lt;?&gt;[] getAnnotatedClasses(Class&lt;?&gt; klass) throws InitializationError {
    Suite.SuiteClasses annotation = klass.getAnnotation(Suite.SuiteClasses.class);
    if (annotation != null) {
      return annotation.value();
    }
    throw new InitializationError(String.format(&quot;class '%s' must have a SuiteClasses annotation&quot;, klass.getName()));
  }

  private static List getRunners(Class&lt;?&gt;[] classes) throws Throwable {
    List runners = new LinkedList();
    for (Class&lt;?&gt; klazz : classes) {
      runners.add(createRunner(klazz));
    }
    return runners;
  }

  private static Runner createRunner(Class&lt;?&gt; klazz) throws Throwable {
    return isSuite(klazz) ? new TestLinkSuite(klazz,null) : new TestLinkRunner(klazz);
  }

  public static boolean isSuite(Class&lt;?&gt; klazz) {
    return klazz.getAnnotation(Suite.SuiteClasses.class) != null;
  }

  /**
    *
    * Internal runner implementation
    *
   */
 private static class TestLinkRunner extends AbstractReportingTestRunner {

 public TestLinkRunner(Class&lt;?&gt; klass) throws Throwable {
 super(klass);
 System.out.println(String.format(&quot;TestLinkRunner created for running test class %s&quot;,klass.getSimpleName()));
 try {
 filter(new TestLinkFilter());
 } catch (NoTestsRemainException e) {
 System.out.println(&quot;No TestLink tests found in &quot; + klass.getSimpleName());
 }
 }

 @Override
 protected TestReporter getTestReporter() {
 return s_testReporter;
 }
 }

 /**
 * Filter implementation that filters out
 * 1. test methods missing the TestLink annotation
 * 2. test classes that has no methods annotated with TestLink
 * 3. test suites that has no test classes containing methods annotated with TestLink
 */
 private static class TestLinkFilter extends Filter {

 @Override
 public boolean shouldRun(Description description) {
 if (isTestLinkMethod(description)) {
 return true;
 }
 for (Description each : description.getChildren()) {
 if (shouldRun(each)) {
 return true;
 }
 }
 return false;
 }

 private boolean isTestLinkMethod(Description description) {
 return description.isTest() &amp;&amp; description.getAnnotation(TestLink.class) != null;
 }

 @Override
 public String describe() {
 return &quot;TestLink&quot;;
 }
 }
}
</pre>
<p>Observera att <code>TestLinkSuite</code> ärver <code>Suite</code>, dvs skall användas för att exekvera en testsvit och inte enbart en enskild testklass. Denna begränsning är i praktiken inget problemet, om du mot all förmodan endast har en testklass som exekverar alla dina integrationstester så är det en smal sak att skapa en testsvit som inkluderar din enda testklass.</p>
<p>Annoteringen TestLink används för att annotera en testmetod, för vilken resultatet skall importeras in i TestLink.  Utöver att fungera som &#8221;metodmarkör&#8221;, innehåller den också testfallsid och testfallsbeskrivning. Id används för att mappa testmetod mot testfall i TestLink medan beskrivningen används för att uppdatera noteringsfältet i TestLink.</p>
<pre class="brush: java; title: ;">

package se.cygni.testlink;

import static java.lang.annotation.ElementType.METHOD;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface  TestLink {
String id();
String desc() default &quot;&quot;;
}
</pre>
<p>Nu återstår bara att fixa till en testsvit som exekveras av <code>TestLinkSuite</code>. Din testsvit måste också implementera en <code>@BeforeClassmetod</code> för att där skapa den &#8221;globala&#8221; <code>TestLinkReporter</code> med uppgifter om projekt, testplan mm som skall uppdateras med testresultatet och injektas i <code>TestLinkSuite</code>.</p>
<p>Först en testklass med <code>@TestLink-</code>annoteringar för att indikera att testerna skall köras och att resultatet skall importeras in i TestLink</p>
<pre class="brush: java; title: ;">

package se.cygni.testlink;

import static org.junit.Assert.*;

import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;

import se.cygni.testreport.Calculator;

public class TestLinkCalculatorTest {

  @Test
  @TestLink(id=&quot;421&quot;, desc=&quot;Test addition&quot;)
  public void test_addition() {
   assertEquals(5,Calculator.add(2,3));
  }

  @Test
  @TestLink(id=&quot;422&quot;, desc=&quot;Test substraction&quot;)
  public void test_subtraction() {
    assertEquals(5,Calculator.subtract(8,2));
  }

}
</pre>
<p>Slutligen en testsvit som inkluderar ovanstående test och som exekveras av <code>TestLinkSuite</code>.</p>
<pre class="brush: java; title: ;">

package se.cygni.testlink;

import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(TestLinkSuite.class)
@SuiteClasses(TestLinkCalculatorTest.class)
public class CalculatorTestSuite {
  @BeforeClass
  public static void beforeClass() {
    TestLinkReporter testLinkReporter = new TestLinkReporter(Configuration.getTestLinkProject(),Configuration.getTestPlan(),Configuration.getTestPlatform(),Configuration.getTestBuild());
    TestLinkSuite.setTestLinkReporter(testLinkReporter);
  }

}
</pre>
<p>Även om implementation ovan är specifikt riktad mot TestLink så är själva grundidén, dvs hur du designar dina egna testrunners och testklasser för att få till en rapportering av testresultatet generell och tillämplig även för andra testhanteringssystem än TestLink. Under förutsättning förstås att det finns ett användbart java-API!</p>
<p>Detta avslutar denna artikelserie om automatiserade integrationstester &#8211; kolla gärna in del 1 som handlar om parallelliserade tester eller del 2 som handlar om parametriserade tester.</p>

	Tags: <a href="http://cygni.se/taggar/eclipse/" title="eclipse" rel="tag">eclipse</a>, <a href="http://cygni.se/taggar/java/" title="java" rel="tag">java</a>, <a href="http://cygni.se/taggar/junit/" title="junit" rel="tag">junit</a>, <a href="http://cygni.se/taggar/parallell/" title="parallell" rel="tag">parallell</a>, <a href="http://cygni.se/taggar/parametriserad/" title="parametriserad" rel="tag">parametriserad</a>, <a href="http://cygni.se/taggar/python/" title="python" rel="tag">python</a>, <a href="http://cygni.se/taggar/rpc/" title="rpc" rel="tag">rpc</a>, <a href="http://cygni.se/taggar/ruby/" title="ruby" rel="tag">ruby</a>, <a href="http://cygni.se/taggar/spring/" title="spring" rel="tag">spring</a>, <a href="http://cygni.se/taggar/teknik/" title="teknik" rel="tag">teknik</a>, <a href="http://cygni.se/taggar/test/" title="test" rel="tag">test</a>, <a href="http://cygni.se/taggar/testlink/" title="testlink" rel="tag">testlink</a>, <a href="http://cygni.se/taggar/xml/" title="xml" rel="tag">xml</a><br />
]]></content:encoded>
			<wfw:commentRss>http://cygni.se/2013/01/07/integrationstester-testlink-och-junit/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Summering av 2012</title>
		<link>http://cygni.se/2012/12/21/summering-av-2012/</link>
		<comments>http://cygni.se/2012/12/21/summering-av-2012/#comments</comments>
		<pubDate>Fri, 21 Dec 2012 14:05:35 +0000</pubDate>
		<dc:creator>Jon Persson</dc:creator>
				<category><![CDATA[Cygnibloggen]]></category>

		<guid isPermaLink="false">http://cygni.se/?p=4652</guid>
		<description><![CDATA[Nu är det bara en dryg vecka kvar av 2012 och för oss på Cygni går avslutningen i dur. Trots signaler om att Sverige på allvar håller på att dras in i den europeiska lågkonjunkturen tuffar Cygni på bättre än någonsin. Vi växer i lagom takt med en mycket medveten och selektiv rekryteringsprocess. Vi skaffar ...]]></description>
			<content:encoded><![CDATA[<p>Nu är det bara en dryg vecka kvar av 2012 och för oss på Cygni går avslutningen i dur. Trots signaler om att Sverige på allvar håller på att dras in i den europeiska lågkonjunkturen tuffar Cygni på bättre än någonsin. Vi växer i lagom takt med en mycket medveten och selektiv rekryteringsprocess. Vi skaffar nya kunder och ramavtal och vi fortsätter att vara våra kunders favoritleverantör. Det senare fick jag i veckan ytterligare ett bevis för då jag pratade med en telekomoperatör som ville förstärka sitt team med en vass backendutvecklare. På frågan om jag skulle skicka över en konsultprofil fick jag svaret ”det känns som lite overkill att be Cygni skicka konsultprofiler, vi vet ju att ni bara har stjärnkonsulter”…</p>
<p>Cygni-året 2012 inleddes med en Camel-föreläsning på konsultmötet i februari. I mars fick vi den klassiska kombinationen fisksoppa och guidad visning på Fotografiska museet. Någon vecka senare flög 37 Cygnianer till Champoluc för konfererande, umgänge och frisk alpluft. I april besökte Oracle vårt konsultmöte och snackade Java och i maj fick vi en dragning om responsive design följt av gemensamt hackande.</p>
<p>Eftersom Fotbolls-EM gick av stapeln i juni var temat för vår kundfest givet:<br />
<img src="http://cygni.se/wp-uploads/2013/01/f2.png" alt="" title="f2" width="438" height="376" class="alignleft size-full wp-image-4654" />
<div style="clear:both"></div>
<p>Bara ett par veckor efter kundfesten var det dags för sommarmiddag och på schemat stod segeltävling.<br />
<img src="http://cygni.se/wp-uploads/2013/01/sommarmiddag2012-300x165.png" alt="" title="sommarmiddag2012" width="300" height="165" class="alignleft size-medium wp-image-4656" />
<div style="clear:both"></div>
<p>40-talet Cygnianer skulle, efter en summarisk genomgång av grundläggande seglingsprinciper, ge sig ut på en fjärd utanför Värmdö och utkämpa ett sjöslag. Kvällen avslutades inne på Södra Bar och som vanligt kom många Cygnianer inte hem förrän solen hade gått upp.</p>
<p>Hösten rivstartade med after work fredag 24 augusti följt av Bellmanstafetten dagen efter. Hårt schema för dem som var med på bägge aktiviteterna. Ett par veckor senare åkte vi ut till Fejan för kickoff och pokerprogrammeringstävling.</p>
<p>Konsultmötena under hösten har inkluderat highlights som Continuous Delivery, Solr och Neo4J. Det sista konsultmötet följde en gammal fin Cygni-tradition och innebar restaurangbesök på stan. Vi gick gruppvis och för första gången var den nya Cygni-gruppen Sirius ute tillsammans!</p>
<p>Julmiddagen på Berns, som Åsa fixat galant, tycker jag blev otroligt bra. Kombinationen av att både kunna hänga i den publika baren och att kunna sitta och snacka i ett chambre separée, var något jag tycker vi ska försöka få till fler gånger.</p>
<p>Under året har Cygnis första personalråd sett dagens ljus och jag är imponerad (men inte förvånad!)  av hur seriöst och engagerat de tog sig an uppgiften.</p>
<p>De nya Cygnianer som vi haft förmånen att välkomna under året har redan gjort stort avtryck både internt och hos våra kunder. När vi nu går i mål för året gör vi det med full beläggning och har dessutom 8 underkonsulter i uppdrag. Och blickar vi framåt mot nästa år så fortsätter det att se oerhört bra ut.</p>
<p>Sjunde mars åker vi till Grindelwald för att konferera. Denna ort är paragliding-stället #1 i Schweiz och alla Cygnianer som vill kommer att få möjlighet att testa på. Letandet efter ett nytt och större kontor fortsätter 2013. För några veckor sedan trodde jag att vi hade ett kontor vid Slussen på gång men tydligen låg ett annat företag före oss i processen. Det vi söker efter är en lokal som ligger bra till kommunikationsmässigt dvs nära centralen eller slussen.</p>
<p>Den 1 februari skapas också en ny konsultgrupp på Cygni – Sirius. Som ni vet är det Per som kommer ta rollen som konsultchef och jag är väldigt förhoppningsfull inför detta.</p>
<p>Precis som förra året delar vi inte ut några fysiska julklappar på Cygni. Istället skänker vi 50.000 kr till BRIS. Surfa gärna in på <a href="http://www.brisstodforetag.se">www.brisstodforetag.se</a> och läs om vad Cygnis pengar bidrar till!</p>
<p>Väl mött Cygni-året 2013!</p>
Inga taggar hittades]]></content:encoded>
			<wfw:commentRss>http://cygni.se/2012/12/21/summering-av-2012/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
