Her skriver jeg om ulike verktøy som interesserer meg som utvikler. Produktivitetsverktøy og Visual Studio add-ins, continuous integration tools, ulike .net biblioteker.., alt får plass i denne kategorien.

Thomas lager verktøy [Luke 24, 2012]

Monday, December 24th, 2012
Ingen kommentarer

Thomas Kjeldahl Nilsson (@thomanil) er blant annet mannen bak Mine Verktøy, hvor han i drøyt et år publiserte en rekke intervjuer hvor norske geeks fortalte hvilket verktøysoppsett de hadde. Dagens blogpost – på selveste julaften – følger i det samme sporet. Thomas snakker om noe som er veldig viktig for en effektiv utvikler; kunsten å lage sine egne verktøy!

Thomas blogger også på kjeldahlnilsson.net.

colorPortrait

Hvem er du?
Trodde jeg skulle bli kjemiker på Blindern – men fant ut at jeg hadde mye lettere for programmering, gitt!

Hva er jobben din?
Partner og programmerer hos Gitorious AS – vi utvikler en fullstendig open source løsning for Git-hosting, og lever av å selge hosting og knallgod support til kunder over hele verden.

Hva kan du?
Pragmatisk allrounder som er spesielt glad i entreprenørskap, web-teknologi… og Emacs.

Hva liker du best med yrket ditt?
Å kunne skape noe ut av ingenting.


Verktøy for å lage verktøy: en Unix-safari

2239102681_bb9ca36abc_z

Jeg er fascinert av smiekunst.

Tradisjonelle smeder jobber med enkle, grunnleggende verktøy: esse, ambolt, hammer og tang. Smeden kan imidlertid lage mer effektive og spesialiserte verktøy ved behov. Det er såvidt jeg vet ingen andre håndtverkere som kan trekke seg selv opp etter bukseselene på denne måten…

unntatt oss programmerere. Med gode basis-verktøy kan vi bygge alt vi trenger for å jobbe raskere og mer effektivt.

I denne artikkelen skal vi se hvordan Unix-miljøer enkelt lar oss bruke basisverktøy til å bygge nye verktøy og arbeidsflyter. Vi jobber på tre nivåer: rett på kommandolinja, med shellscripting, og med Ruby.

Unix-idealene

Et av de beste utvikler-verktøyskrinene finner du i Unix-systemene (Linux, OS X, BSD, mfl): der får du mange små, fokuserte og gjenbrukbare byggeklosser du kan sette sammen til nye programmer. Med andre ord: de fleste verktøy du trenger enten finnes allerede, eller kan lages ved å kombinere allerede eksisterende verktøy.

Det er nyttig å omfavne — eller i det minste forstå — Unix-kulturen, for å absorbere en del prinsipper som har gjennomsyret denne tradisjonen gjennom flere tiår. Eric S Raymond oppsummerer disse prinsippene slik:

  • Modularitet: bygg enkle deler, koblet sammen med ryddige grensesnitt.
  • Klarhet: lettforståelige løsninger er bedre enn “lure knep”.
  • Komposisjon: design programmer slik at de kan kobles sammen med andre programmer.
  • Løskobling: skill kontrakt fra mekanisme, grensesnitt fra implementasjon.
  • Enkelhet: etterstreb enkelhet. Tillat bare kompleksitet der du er nødt.
  • Tilbakeholdenhet: bygg bare et stort program når det er helt nødvendig.
  • Transparens: gjør programmer synlige og åpne — det gjør testing og feilsøking enklere.
  • Robusthet: programmer som er enkle og åpenbare blir også mer robuste.
  • Representasjon: legg domenelogikk i dataen din, slik at selve programmet kan forbli rettfrem og robust.
  • Forutsigbarhet: grensesnittene dine bør være innlysende, ikke “overraskende”.
  • Stillhet: hvis et program ikke har noe nyttig å si, bør det holde kjeft og bare gjøre jobben sin.
  • Feilhåndtering: dersom programmet må feile, feil så tidlig og høylydt som mulig.
  • Økonomi: utvikler-tid er den mest verdifulle ressurssen din, så stress maskinen istedet for programmereren.
  • Generering: unngå å skrive ting for hånd. Forsøk istedet å skrive programmer som skriver programmer.
  • Optimalisering: prototyping før polering. Få det til å fungere før du optimaliserer det.
  • Mangfold: det er mange veier til Rom. Vær skeptisk til vedtatte sannheter og fasiter.
  • Utvidbarhet: design for fremtiden, den er her før du tror.

Disse prinsippene gjør deg til en bedre utvikler selv om du ellers ikke bruker Unix-systemer i det hele tatt.

Scenario: vi bygger en arbeidsbenk for email

Den beste måten å få dette til å synke inn er å bruke Unix-verktøy til å bygge noe konkret. Vi skal derfor bruke grunnleggende Unix-programmer til å bygge en enkel arbeidsflyt for å jobbe med Gmail-innboksen din.

Infrastruktur: installasjon av offlineimap og msmtp

Fundamentet for emailklienten vår blir verktøyene offlineimap (for å hente mail) og msmtp (for å sende mail).

Når disse er installert kommer vi til å ha en Maildir katalog som er synkronisert med Gmail-kontoen vår via IMAP, og vi kommer til å ha en enkel kommando for å sende mail rett fra kommandolinja. Begge verktøyene er tilgjengelig på både Linux, OS X og andre Unix-varianter.

Hvis du vil følge eksemplene mine på din egen maskin så bør du få dette til å fungere først. Hvis du bare vil lese resten av artikkelen kan du hoppe over denne seksjonen.

Merk: Jeg har bare testet oppsettet beskrevet nedenfor i Ubuntu — andre Unix-varianter kan være litt forskjellige.

offlineimap

I Ubuntu installerer du offlineimap slik:

sudo apt-get install offlineimap

Vi oppretter så en lokal Maildir katalog der vi vil at offlineimap skal legge den lokale kopien av gmail kontoen vår:

mkdir ~/Maildir

For å bruke verktøyet må vi konfigurere det til å snakke med vår egen gmail-konto. Vi oppretter fila ~/.offlineimaprc, og setter korrekte skrive/lese-rettigheter:

touch ~/.offlineimaprc && chmod 600 ~/.offlineimaprc

Vi fyller den med følgende innhold (oppdater til å matche din egen Gmail-konto):

[general]
accounts = gmail

# Kutt ut all output, med unntak av feilmeldinger
ui = quiet

[Account gmail]
localrepository = gmail-local
remoterepository = gmail-remote
status_backend = sqlite

[Repository gmail-local]
type = Maildir
localfolders = ~/Maildir/Gmail

[Repository gmail-remote]
type = Gmail
remoteuser = DIN_GMAIL_EMAIL_ADRESSE
remotepass = DITT_GMAIL_PASSORD

# Vi henter kun ned selve innboksen, ikke resten av folderne
folderfilter = lambda folder: folder in ['INBOX']

# Sletting lokalt skal ikke medføre sletting i Gmail-kontoen
realdelete = no

La oss teste om verkøyet fungerer som det skal. Kjør kommandoen offlineimap. Hvis konfigurasjonen din er korrekt så vil hele innboksen din plukkes ned og legges i katalogen Maildir i rota av hjemmeområdet ditt. Vær tålmodig: dette kan ta en stund hvis du har mye liggende i selve innboksen i gmail.

msmtp

I Ubuntu installerer du msmtp-pakken slik:

sudo apt-get install msmtp

Også dette verktøyet må konfigureres: vi oppretter fila ~/.msmtprc, og gir den korrekte skrive/lese rettigheter:

touch ~/.msmtprc && chmod 600 ~/.msmtprc

Vi fyller den med følgende innhold (oppdater til å matche din egen Gmail-konto):

defaults
auth on
tls on

account         gmail
host            smtp.gmail.com
port            587
from            DIN_GMAIL_EMAIL_ADRESSE
user            DIN_GMAIL_EMAIL_ADRESSE
password        DITT_GMAIL_PASSORD
tls_trust_file  /etc/ssl/certs/ca-certificates.crt

La oss teste om verkøyet fungerer som det skal. Kjør følgende kommando (bare bruk din egen mailadresse):

echo 'tester msmtp...' | msmtp -a gmail 'thomas@kjeldahlnilsson.net'

Sjekk så emailen din som vanlig. Dersom msmpt ble konfigurert riktig så skal du ha mottatt en mail fra deg selv nå med innholdet “tester msmtp…”

Fungerer alt som det skal? Flott, da fortsetter vi.

Kataloger, filer og strenger er alt vi trenger

Etter at vi har kjørt offlineimap første gang ender vi opp med en katalog ~/MailDir/Gmail, som inneholder underkataloger som representerer mapper og tilstander i hver mailfolder. Hver mail i disse folderne er lagret som en vanlig flatfil.

Folderstrukturen ser omtrent slik ut:

Maildir/
└── Gmail
    └── INBOX
        ├── cur
        │   └── 1352560840_1.25056.localhost,U=2,FMD5=7e33429f656f1e6e9d79b29c3f82c57e:2,S
        ├── new
        │   ├── 1352560840_0.25056.localhost,U=1,FMD5=7e33429f656f1e6e9d79b29c3f82c57e:2,
        │   └── 1352560841_0.25056.localhost,U=3,FMD5=7e33429f656f1e6e9d79b29c3f82c57e:2,
        └── tmp

Filnavnene ser litt kryptiske ut, fordi offlineimap bruker filnavnene til å lagre en del informasjon om hver email: unike IDer, checksums etc. Filene under cur katalogen inneholder leste email, mens uleste mailer ligger under new.

Tilstand i denne lokale mailboksen synkroniseres til gmail-kontoen vår hver gang vi kjører offlineimap kommandoen. For eksempel kan vi flytte en email fra new til cur mappen og så kjøre offlineimap. Dette vil sette statusen på email til “lest” i Gmail-kontoen sentralt også.

Innholdet av en email ser ut omtrent slik:

MIME-Version: 1.0
Received: by 10.112.4.227; Sat, 10 Nov 2012 07:18:48 -0800 (PST)
Date: Sat, 10 Nov 2012 07:18:48 -0800
Message-ID: <CABQd01N+VFn89guaFCcBu+x=rJudno+yarZPZF1=CqWDz=sQnw@mail.gmail.com>
Subject: Import your contacts and old email
From: Gmail Team <mail-noreply@google.com>
To: Kensei Test Account <kensei.test@gmail.com>
Content-Type: multipart/alternative; boundary=00151748de9ec3315804ce259570

--00151748de9ec3315804ce259570
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable

You can import your contacts and mail from Yahoo!, Hotmail, AOL, and many
other web mail or POP accounts. If you want, we'll even keep importing your
mail for the next 30 days.
     Import contacts and mail
=BB<https://mail.google.com/mail/#settings/accounts>

We know it can be a pain to switch email accounts, and we hope this makes
the transition to Gmail a bit easier.

- The Gmail Team

Please note that importing is not available if you're using Internet
Explorer 6.0. To take advantage of the latest Gmail features, please upgrad=
e
to a fully supported
browser<http://support.google.com/mail/bin/answer.py?answer=3D6557&hl=3Den&=
utm_source=3Dwel-eml&utm_medium=3Deml&utm_campaign=3Den>
.

--00151748de9ec3315804ce259570
Content-Type: text/html; charset=ISO-8859-1

<html>
<font face="Arial, Helvetica, sans-serif">
<p>You can import your contacts and mail from Yahoo!, Hotmail, AOL, and many
other web mail or POP accounts. If you want, we'll even keep importing your
mail for the next 30 days.</p>

<table cellpadding="0" cellspacing="0">
<col style="width: 1px" /><col /><col style="width: 1px" />
<tr>
  <td></td>
  <td height="1px" style="background-color: #ddd"></td>
  <td></td>
</tr>
<tr>
  <td style="background-color: #ddd"></td>
  <td background="https://mail.google.com/mail/images/welcome-button-background.png"
      style="background-color: #ddd; background-repeat: repeat-x"
    ><a href="https://mail.google.com/mail/#settings/accounts"
        style="font-weight: bold; color: #000; text-decoration: none; display: block; padding: 0.5em 1em"
      >Import contacts and mail &#187;</a></td>
  <td style="background-color: #ddd"></td>
</tr>
<tr>
  <td></td>
  <td height="1px" style="background-color: #ddd"></td>
  <td></td>
</tr>
</table>

<p>We know it can be a pain to switch email accounts, and we hope this makes
the transition to Gmail a bit easier.</p>

<p>- The Gmail Team</p>

<p><font size="-2" color="#999">Please note that importing is not available if
you're using Internet Explorer 6.0. To take advantage of the latest Gmail
features, please
<a href="http://support.google.com/mail/bin/answer.py?answer=6557&hl=en&utm_source=wel-eml&utm_medium=eml&utm_campaign=en"><font color="#999">
upgrade to a fully supported browser</font></a>.</font></p>

</font>
</html>

--00151748de9ec3315804ce259570--

Hvorfor er dette nyttig?

Siden mailboksen vår er representert med vanlige kataloger, filer og strenger så kan vi bruke standard Unix-verktøy for å lese og manipulere email fra kommandolinja. Nå kan vi begynne å leke med verktøykassa vår!

“In The Beginning was the Command Line”

Unix-verktøy følger noen felles konvensjoner for å ta imot data og og sende resultater ut.

Programmer kjørt på kommandolinja tar data inn på STDIN strømmen, og spytter resultater ut igjen på strømmen STDOUT (med unntak av feilmeldinger , som spyttes ut på STDERR strømmen). Program-output ender enten i terminalen, eller kan sendes videre til et annet program.

Siden disse felles konvensjonene finnes kan vi kombinere programmer: Ved å kjede sammen flere verktøy på samme linje med | (pipe) mellom hver av dem, kan vi la data flyte gjennom dem etter tur som i et vannrør. Vi kan med andre ord enkelt bygge verktøy som består av pipelines av andre kommandoer.

Et eksempel: denne linja skrev jeg forrige uke for å finne alle sannsynlige synkroniserings-konflikter i Dropbox-mappa mi.

find ~/Dropbox | grep conflicted

Find-kommandoen lister opp alle filer, rekursivt, under den angitte katalogen. Hvert filnavn pipes videre til grep-kommandoen, som beholder de filstiene som har inneholder ‘conflicted’. Listen av filnavn ender så i terminalen min slik at jeg kan se hvilke filer jeg antagelig bør rydde vekk. Det finnes mer optimale måter å gjøre denne oppgaven, men poenget er at det tok meg bare noen sekunder å lage denne automatiseringen.

Å kjede sammen pipelines av filtere, transformasjoner etc føles svært naturlig dersom du allerede er komfortabel med å bruke map, filter og reduce-operasjoner i “ordentlige” programmeringsspråk.

La oss bygge noen verktøy for email.

Terminal-snutt: Send en mail

Denne har du kanskje allerede kjørt for å teste at msmtp fungerer.

echo 'Dette er body i en mail jeg sender fra terminalen' | msmtp -a gmail 'dinadresse@gmail.com'

echo skriver ut den påfølgende teksten til STDOUT. Hvis den står for seg selv så får vi teksten tilbake ut i terminalen. Her piper vi istedet teksten til msmtp, som bruker teksten den får inn på STDIN til som body i en ny mail.

Terminal-snutt: Sjekk antall uleste email

Følgende linje teller hvor mange uleste email vi har.

find ~/Maildir/Gmail/INBOX/new -type f | wc -l

Vi finner alle filer i new folderen (kun filer, ikke kataloger etc), og bruker wc til å telle hvor mange av dem det var. Jeg synes det er litt knapt å bare dumpe ut tallet, så jeg legger til litt beskrivende tekst også:

echo "Unread emails: $(find ~/Maildir/Gmail/INBOX/new -type f | wc -l)"

Nå har vi alt vi trenger for å lage en liten “widget” for å vise antall uleste email. Vi gjør det ved å ha et lite terminalvindu på desktopen vår, der vi kjører følgende kommando for å sette den igang:

watch -n10 'offlineimap && echo "Unread emails: $(find ~/Maildir/Gmail/INBOX/new -type f | wc -l)"'

watch kjører de påfølgende kommandoene hvert tiende sekund — vi synkroniserer først mailen vår, så kjører vi programsnutten vår for å telle uleste meldinger.

Nå har vi et terminalvindu som hele tiden ser slik ut, og blir oppdatert hvert 10 sekund:

Every 10.0s: offlineimap && echo "Uleste mail: $(find ~/Maildir/Gmail/INBOX/new -type f | wc -l)"          Fri Nov 30 21:14:11 2012

Uleste mail: 1

Terminal-snutt: Innboks-oversikt

En naturlig start er å lage oss et sammendrag av hvilke mail vi har liggende i innboksen vår. Her er en måte å skrive ut en liste over mailene våre:

grep -Rh ^Subject: ~/Maildir/Gmail/INBOX

Vi søker rekursivt (R) i innboks-folderen vår etter tilfeller av <Linjestart> Subject:. H-en er der for å kun skrive ut subjekt-linjen, ikke det kryptiske filnavnet i tillegg (grep skriver ellers ut både filnavn og hvordan treffet ser ut).

Dette bør treffe en gang i hver fil (vi går ut fra at dette er standardformatet på mailfilene og at det forhåpentligvis bare er en slik linje i hver fil). Disse matchende emne-linjene skrives ut til terminalen vår, og ser omtrent slik ut når vi kjører kommandoen vår:

➜  ~  grep -Rh ^Subject:  ~/Maildir/Gmail/INBOX
Subject: Get Gmail on your mobile phone
Subject: Import your contacts and old email
Subject: Customize Gmail with colors and themes

Nå har vi oversikt over innboksen vår.

Terminal-snutt: Les en mail

Vi bør også kunne lese en bestemt mail. Denne linja gjør det mulig å dumpe ut innholdet av mail nummer N fra lista ovenfor, telt fra toppen. Denne er noe mer innfløkt:

find ~/Maildir/Gmail/INBOX -type f | sed -n 2p | xargs cat

Vi finner alle filer rekursivt under innboksen vår. Vi plukker den nte linja i fil-lista (i dette tilfelle nummer to), og sender fil-stien til cat kommandoen, som bare dumper ut innholdet av den gitte fila i terminalen vår.

Disse email-kommandoene vi har laget her fungerer jo helt greit, men de er ikke så smidige å jobbe med hvis du trenger å bruke dem raskt og ofte. Det er på tide å ty til shellscripting for å forenkle og gjenbruke.

Sidespor: ta vare på småting du lærer og bygger

Jeg har problemer med å huske hendige terminal-snutter den første gangen jeg bruker dem. Her er noen grep som hjelper:

  • Du kan søke bakover i historikken din ved å trykke Ctrl-r. Hvis du begynner å skrive får du opp første mulige treff, og kan trykke pil-opp for å hoppe til neste kandidat som matcher søket bakover i historikken din. Og sørg for at terminalen din er satt til å bevare mye, eller all, historikk bakover i tid!
  • Personlige “cheatsheets”. Jeg har en orgmode-fil der jeg skriver ned kjekke one-liners, verktøy, program-snutter etc jeg kommer over — mens jeg jobber, fra artikler og bøker, fra kollegaer og så videre. Jeg er ikke så flink til å ta til meg nye greier på første forsøk, så jeg liker å skrive ting opp og vende tilbake til dem senere for å repetere og gjenoppdage.
  • Definer aliaser i terminal-miljøet ditt. Hvis du bruker Bash så oppretter eller oppdaterer du fila ~/.bashrc, og kan legge til linjer på denne formen:
alias helloworld="echo 'hello world'"

Når du har lastet miljøet på nytt vil du kunne bruke aliaset som en hvilken som helst annen kommando/script. Vi kan for eksempel forenkle en av linjene vi skrev ovenfor:

alias min_innboks="grep -Rh ^Subject: ~/Maildir/Gmail/INBOX"

Nå blir det litt enklere å sjekke mailboksen:

➜  ~  min_innboks
Subject: Get Gmail on your mobile phone
Subject: Import your contacts and old email
Subject: Customize Gmail with colors and themes

Når oneliners ikke strekker til banker shellscripting på døra

Vi kommer til et punkt der vi trenger mer faktisk programmering for å få ting gjort. Med andre ord: variable, conditionals, løkker, og ikke minst muligheten til å spre logikken over flere linjer.

La oss lage bash-scripts av operasjonene vi gikk gjennom i forrige seksjon. Da kan vi forbedre dem ved å gjøre dem tilgjengelige som kortere, parametriserte kommandoer.

All kode vi skriver her og i de neste seksjonene er forøvrig tilgjengelig for nedlasting.

Shellscript: Send en mail

Vi lager et script som heter send-email, som tar mottaker og mail-tekst inn som parametre.

#!/bin/sh

RECIPIENT=$1
TEXT=$2
echo $TEXT | msmtp -a gmail $RECIPIENT

Den aller første linja er en såkalt shebang som forteller hvordan scriptet skal eksekveres (i dette tilfellet sier vi at programmet er et shellscript). $1, $2 etc er variabler som bindes til å inneholde til shellscriptet. For lesbarhetens skyld tilegner vi dem til nye variable, før vi kjører samme kommando som vi gjorde ovenfor for å sende emailen.

Hvis du putter send-email i din egen path kan du kjøre den slik:

send-email thomanil@gmail.com "Sent from a tiny shellscript"

Dette verktøyet vårt ble litt mer brukervennlig nå, ikke sant?

Shellscript: Sjekk antall uleste email

Kommandoen vår for å holde styr på antall uleste mail gjør vi om til et script som heter watch-unread-emails, og ser slik ut:

#!/bin/sh

POLLING_INTERVAL=$1
watch -n$POLLING_INTERVAL 'offlineimap && echo "Unread emails: $(find ~/Maildir/Gmail/INBOX/new -type f | wc -l)"'

Kommandoen vår tar en parameter: antall sekunder mellom hver gang kommandoen skal kjøres/oppdateres. Slik kjøres den når scriptet er i pathen din:

watch-unread-emails 10

Shellscript: Innboks-oversikt

Vi lager en kommando som heter display-inbox for å liste opp innholdet i innboksen vår.

#!/bin/sh

grep -Rh ^Subject: ~/Maildir/Gmail/INBOX

Denne er bare den samme pipelinen vi tidligere skrev direkte inn i terminalen — bare mer brukervennlig siden vi slipper å huske den grep-kommandoen.

Når scriptet er tilgjengelig i pathen din kan du eksekvere det slik:

display-inbox

Shellscript: Les en mail

Linja vi tidligere lagde for å lese innhold av en bestemt mail var rimelig knotete, så det blir fint å forenkle litt.

#!/bin/sh

MAIL_NUMBER=$1
SED_COMMAND=$(printf "sed -n %sp" $MAIL_NUMBER)
find ~/Maildir/Gmail/INBOX -type f | $SED_COMMAND | xargs cat

Scriptet tar “mail nummer N fra toppen” som argument. Vi velger å bygge opp sed-kommandoen separat underveis for å gjøre det hele mer lesbart.

Når scriptet ligger i pathen din kan du kjøre det på følgende måte.

read-email 2

Helt klart enklere enn den kryptiske linja vi måtte skrive inn før.

Sidespor: gjør det enkelt for deg selv å scripte

Hvis du gjør terskelen for å skrive nye scripts så lav som mulig, så lager du også fler av dem og gjør derfor mer for å forbedre arbeidsflyten din. Her er to grep som hjelper:

  • Lag en katalog i hjemmeområdet ditt som er dedikert til nye scripts, f.eks ~/bin eller ~/scripts. Legg til denne katalogen i pathen slik at scriptene dine er tilgjengelige i miljøet ditt overalt. Bonus-poeng: lag en git-repo av script-katalogen din slik at har versjonskontroll, og hvis du jobber på flere maskiner, legg katalogen i Dropbox-folderen din og symlink dit fra alle maskinene du sitter på.
  • Lag et program som enkelt lar deg generere nye scripts. Under ser du mitt ~/script/generatescript bash-script. Det tar navn på nytt script som argument, oppretter det i standard-katalogen min (med riktige rettigheter), og fyrer opp standard-editoren min slik at jeg umiddelbart kan begynne å arbeide på det nye scriptet.
#!/bin/sh

SCRIPTPATH=~/scripts/$1
echo '#!/bin/sh
# Generated, add code here
' >> $SCRIPTPATH

touch $SCRIPTPATH
chmod a+x $SCRIPTPATH
$EDITOR $SCRIPTPATH

Når shellscripting blir for stygt sier skjønne Ruby hei

Perl kom til verden fordi Larry Wall syntes rå shellscripting ble for primitivt og lite ekspressivt. Etterhvert fikk vi også Ruby, Python, Groovy med fler, som sammen med Perl gjorde Unix-scripting langt mer behagelig.

Vi skal skrive om kommandoene våre til Ruby. Dette gir oss to umiddelbare gevinster: mer lesbare og utvidbare scripts og, for read-email kommandoen vår, parsing av email via et eksternt Ruby-bibliotek.

Ingenting av det vi gjør i Ruby-koden er umulig å få til i vanlige shellscripts — men Ruby er mer lettlest og vedlikeholdbart.

Ruby-script: Send en mail

Vi skriver om send-email shellscriptet vårt til Ruby:

#!/usr/bin/env ruby

if ARGV.length != 2
  puts "Usage: send-email TO_ADDRESS EMAIL_BODY"
  exit 1
end

recipient = ARGV[0]
text = ARGV[1]
puts `echo #{text} | msmtp -a gmail #{recipient}`

Vi starter scriptet med en ny “shebang”-linje, denne sier at systemet skal bruke ruby kommandoen i brukerens miljø til å eksekvere koden.

Vi legger også til en validering av at riktig antall parametre sendes inn til kommandoen. Argumenter til Ruby-programmer blir lagt i en konstant, global array som heter ARGV. Dersom det er for få eller for mange parametre skriver vi ut en kortfattet bruksanvisning og stopper videre kjøring.

Selve kjøringen av msmtp sheller vi ut til systemet. Dette er fordelen med å bruke Ruby og lignende språk: vi kan delegere ned til det underliggende systemet når som helst. Vi kan derfor velge hvor mye vi vil lene oss på vanlige Unix-verktøy kontra scriptspråkets egne biblioteker — det er ikke en “enten-eller” situasjon.

Ruby-script: Sjekk antall uleste email

Nestemann ut er watch-unread-emails scriptet vårt.

#!/usr/bin/env ruby

if ARGV.length != 1
  puts "Usage: watch-unread-emails POLLING_INTERVAL_SECONDS"
  exit 1
end

polling_interval = ARGV[0].to_i

while true
  new_mail_dir = File.expand_path("~/Maildir/Gmail/INBOX/new/*")
  unread_count = Dir[new_mail_dir].count { |file| File.file?(file) }
  puts `clear && offlineimap`
  puts "Unread emails: #{unread_count}"
  sleep polling_interval
end

Istedet for å lene oss på watch kommandoen så implementerer vi bare tilsvarende logikk selv i Ruby: skriv ut antall uleste email hvert nte sekund.

Vi tømmer terminalen for innhold og synkroniserer email ved å shelle ut til clear kommandoen og offlineimap. Selve antallet email finner vi ved å bruke Rubys egne file-apier istedet for find-kommandoen direkte.

Ruby-scriptet ble her en del lenger enn en det tilsvarende shellscriptet, fordi jeg kapper operasjonene opp i flere steg, variable og linjer. For meg føles dette noe mer utvidbart og lettlest enn det opprinnelige shellscriptet.

Ruby-script: Innboks-oversikt

display-inbox scriptet skriver vi bare om til Ruby for å være konsekvente: Ruby-versjonen sheller ut samme operasjon som tidligere. Jeg syntes at en enkelt grep-linje var helt greit, og det illustrerer at Ruby også kan fungere som en veldig tynn wrapper rundt ordinær shellscripting.

#!/usr/bin/env ruby

puts `grep -Rh ^Subject: ~/Maildir/Gmail/INBOX`

Ruby-script: Les en mail

Til slutt porter vi read-email til Ruby-kode.

#!/usr/bin/env ruby

if ARGV.length != 1
  puts "Usage: read-email EMAIL_NO"
  exit 1
end

#depends on the 'mail' gem, install like this: gem install mail
require 'mail'

maildir = File.expand_path("~/Maildir/Gmail/INBOX")
all_email_filepaths = Dir["#{maildir}/**/*"].select { |f| File.file?(f) }
mail_number = (ARGV[0].to_i)-1
mail_path = all_email_filepaths[mail_number]
mail = Mail.read(mail_path)
puts mail.text_part

Vi bruker Rubys fil-apier til å finne stien som inneholder den nte mailen fra toppen i innboksen. Så drar vi nytte av et eksternt Ruby-bibliotek (en såkalt gem) som heter Mail til å parse email-fila. Til slutt skrives mailen ut som html.

<html>
<font face="Arial, Helvetica, sans-serif">
<p>You can import your contacts and mail from Yahoo!, Hotmail, AOL, and many
other web mail or POP accounts. If you want, we'll even keep importing your
mail for the next 30 days.</p>

<table cellpadding="0" cellspacing="0">
<col style="width: 1px" /><col /><col style="width: 1px" />
<tr>
  <td></td>
  <td height="1px" style="background-color: #ddd"></td>
  <td></td>
</tr>
<tr>
  <td style="background-color: #ddd"></td>
  <td background="https://mail.google.com/mail/images/welcome-button-background.png"
      style="background-color: #ddd; background-repeat: repeat-x"
    ><a href="https://mail.google.com/mail/#settings/accounts"
        style="font-weight: bold; color: #000; text-decoration: none; display: block; padding: 0.5em 1em"
      >Import contacts and mail &#187;</a></td>
  <td style="background-color: #ddd"></td>
</tr>
<tr>
  <td></td>
  <td height="1px" style="background-color: #ddd"></td>
  <td></td>
</tr>
</table>

<p>We know it can be a pain to switch email accounts, and we hope this makes
the transition to Gmail a bit easier.</p>

<p>- The Gmail Team</p>

<p><font size="-2" color="#999">Please note that importing is not available if
you're using Internet Explorer 6.0. To take advantage of the latest Gmail
features, please
<a href="http://support.google.com/mail/bin/answer.py?answer=6557&hl=en&utm_source=wel-eml&utm_medium=eml&utm_campaign=en"><font color="#999">
upgrade to a fully supported browser</font></a>.</font></p>

</font>
</html>

Rå html-kode er ikke så lesbar, men vi kan kanskje bruke Firefox til å lese mailen?

read-email 2 > email.html && firefox email.html

Sidespor: shellscripting eller høynivå språk?

Er det lurt å holde seg så til så enkle verktøy som mulig, eller bør du alltid hoppe rett til det høyeste abstraksjonsnivået i verktøykassa?

Vel, du kan bygge hva som helst bare du har et turing-komplett språk — se for eksempel denne implementasjonen av Tetris i sed — men det er fint å kunne ta steget opp til mer ekspressive språk når vi har behov for det.

Fordelen med moderne scriptspråk som Ruby, Python etc er som nevnt over at de har mer behagelig syntax, og mange hendige eksterne biblioteker. De er også mer kryssplatform enn shellscripting, noe som gjør at du brått kan støtte mer enn bare Unix. For eksempel: ved å bruke Rubys fil-apier så kan scriptet ditt potensielt fungere på Windows også.

Ulempen med de moderne scriptspråkene er at de innfører flere eksterne avhengigheter: hvis du holder deg til shellscripting og standard Unix-verktøy så kan scriptet ditt fungere i minimale Unix-systemer uten å måtte installere flere eksterne pakker.

Det jeg ofte gjør når jeg skal skrive små programmer er å starte med noen enkle Unix-kommandoer i terminalen, og så trekke inn Ruby med en gang jeg ser at ting blir for komplekst for Bash.

Fjellvett: vend i tide, det er ingen skam å snu

Når du plukker opp nye byggeklosser på denne måten så ser du stadig flere løsninger på problemer, og det er fristende å bygge masse greier selv hele tiden. Men: bare fordi du kan bygge hva du vil fra bunnen av, er det ikke alltid lurt å gjøre det.

Vi må velge kampene våre. Noen ganger er det mer pragmatisk å gå ut og kjøpe et kanskje komplekst, suboptimalt, lukket verktøy… som faktisk gjør jobben uten at du trenger å bygge alt selv.

Det kommer an på situasjonen, tenk deg om før du hopper!

Jon Arild leverer kontinuerlig med ConDep [Luke 11, 2012]

Tuesday, December 11th, 2012
4 kommentarer

Jeg ble kjent med Jon Arild Tørresdal i første klasse på barneskolen. Siden det har vi mer eller mindre holdt sammen – vi har til og med jobbet sammen to ganger, først i New Technology Consulting og deretter i CMA Contiki. Jon Arild har alltid vært en sånn fyr som bare fikser ting, og det er en god egenskap. Han løftet NNUG Bergen til nye høyder for noen år tilbake, og nå er han akkurat ferdig med en periode som landsfader for NNUG nasjonalt.

MeWithHat2_lowres

Hvem er du?
Utvikler fra Haugesund med den spede start på gutterommet sammen med Torbjørn, Sharp MZ-700, Amiga 500, Basic, Amos, Cola og Chips :-)

Hva er jobben din?
IT-Arkitekt i Frende Forsikring

Hva kan du?
I tillegg til utvikling – en over norm interesse for OS, nettverk, infrastruktur og automatisering/deployment av disse.

Hva liker du best med yrket ditt?
Variasjon og fleksibilitet – digger å ta tak i ting som ikke er optimalt


ConDep (kort for Continuous Deployment) er en open source DSL (Domene Spesifikt Språk) i C#, som jeg har utviklet pga. lite smidighet rundt automatisering av programvarepublisering i Windows. Hovedfokuset mitt har vært publisering av webapplikasjoner og tilhørende infrastruktur på Windows Servere, men er ikke begrenset til dette.

ConDep er i skrivende stund noen få dager unna RTM, men har vært i pre-release og tilgjengelig på NuGet siden september. I Frende hvor jeg jobber har ConDep vært brukt i nesten 1 år for å publisere våre applikasjoner internt og til produksjon.

I denne artikkelen vil jeg gi en kort introduksjon til bruk av ConDep med et praktisk eksempel. Koden er basert på den nye versjonen som blir tilgjengelig de nærmeste dagene.

Enkelt eksempel – Publisere en web applikasjon til 4 servere uten nedetid

Koden (SomeWebApplication.cs):

using ConDep.Dsl;
using ConDep.Dsl.Builders;
using ConDep.Dsl.Config;

namespace MyDeployment
{
  public class SomeWebApplication :
    ApplicationArtifact,
    IDependOnInfrastructure<MyServerInfrastructure>
  {
    public override void Configure(IOfferLocalOperations local, ConDepConfig config)
    {
      local.ToEachServer(server =>
      {
        server.Deploy
          .Directory(@"C:\SomeWebApp", @"E:\WebApps\SomeWebApp");
      });
    }
  }

  public class MyServerInfrastructure : InfrastructureArtifact
  {
    protected override void Configure(IOfferInfrastructure require)
    {
      require.IIS();
      require.MSMQ();
    }
  }
}

C# koden over kan leses som følger:

Klassen SomeWebApplication arver fra ApplicationArtifact og er avhengig av infrastrukturen som definert i MyServerInfrastructure. Denne infrastrukturen er IIS og MSMQ. Videre konfigureres ConDep fra lokal maskin til å kopiere folderen C:\SomeWebApp til alle servere som er konfigurert(se under).

Konfigurasjonen (prod.env.json):

{
  "LoadBalancer":
  {
    "Name": "jat-nlb01",
    "Provider": "ConDep.Dsl.LoadBalancer.Ace"
  },
  "Servers" :
  [
    {
      "Name" : "jat-web01"
    },
    {
      "Name" : "jat-web02"
    },
    {
      "Name" : "jat-web03"
    },
    {
      "Name" : "jat-web04"
    }
  ]
}

Kommandolinjen:

ConDep.exe MyDeployment.dll Prod

ConDep.exe kommandoen over gjør følgende:

  1. Finner dll’en MyDeployment.dll på disk (det er i denne dll’en C# koden ligger)
  2. Finner konfigurasjonen for Prod (json filen – prod.env.json)
  3. Går til lastbalansereren definert i json-konfigurasjonen og tar den første serveren (jat-web01) offline fra klusteret
  4. Sørger for at riktig infrastruktur finnes på server (jat-web01):
    1. Sjekker at IIS er installert, hvis ikke installeres IIS
    2. Sjekker at MSMQ er installert, hvis ikke installeres MSMQ
  5. Kopierer C:\SomeWebApp til jat-web01 under E:\WebApps\SomeWebApp
  6. Tar jat-web01 online igjen i lastbalanserer
  7. Repeterer steg 3-6 for resterende servere som definert i prod.env.json

Generelt om ConDep

Under panseret bruker ConDep Microsoft WebDeploy for sikkert og smidig kunne kopiere filer og utføre andre operasjoner mellom “klient” og server over http. Dette gjør bl.a. at du slipper å slite med brannmurproblemer o.l.

Alle operasjoner i ConDep er “idempotent”  – dvs. uansett hvor mange ganger du kjører en operasjon, vil du få samme resultat. Dette betyr også at ConDep kun endrer konfigurasjon og innhold dersom de har blitt endret eller ikke eksiterer.

Om du vil at ConDep skal kjøre mot flere miljøer, er det bare å konfigurere opp dette i nye json filer. Mange har f.eks. et test, preprod og prod miljø, som da legges i tre json filer: test.env.json, preprod.env.json og prod.env.json. Sender du inn Test til ConDep.exe er det konfigurasjonen for testmiljøet som vil være gjeldende.

I eksempelet over brukes en provider for lastbalansering mot Cisco ACE, men ConDep har også andre mekanismer for å unngå nedetid under publisering, men som blir utenfor området til denne artikkelen.

www.con-dep.net er det litt mer informasjon om hva ConDep kan gjøre, og mye mer vil dukke om de nærmeste dagene og ukene.

Konklusjon

Tanken bak ConDep er at du skal kunne starte med en helt ren installasjon av en Windows Server, kjøre ConDep mot den, og applikasjonene dine skal bare fungere. Ved å beskrive til ConDep hva som trengs for at applikasjonene dine skal kunne kjøre på en server, samt hvordan ConDep skal flytte applikasjoner fra utviklingsmiljøet ditt (f.eks. en CI server), har du en fullautomatisert prosess som gir deg mye fleksibilitet. Integrert med en CI server gir ConDep deg det du trenger for å implementere Continous Deployment eller Continuous Delivery om du vil.

Fra Windows til Ubuntu

Wednesday, July 4th, 2012
6 kommentarer

Som jeg “annonserte” for et par uker siden var det nå på tide å bytte ut min tre år gamle laptop, og jeg endte etter mye frem og tilbake opp med å bestille en Lenovo W530. Denne blogposten handler litt om den nye laptop’en, men først og fremst handler det om at jeg har valgt å forsøke å bytte til Linux som mitt primære OS, og hvordan jeg som Windows/.NET-utvikler har tenkt å få det til å fungere.

Min nye Lenovo

Laptopen ankom på mandag. Docking og SSD ankom på tirsdag. Jeg venter fortsatt på 8 av de 16 Gigabytene med minne jeg har tenkt å bruke.

Førsteinntrykket er gjevnt over bra. Den er ikke like sexy som en Sony Vaio eller Asus ZenBook, men en kraftmaskin av høy kvalitet. Den er også lettere enn min gamle Dell. Den har en bra touchpad, og HD-skjermen er super – en 16:9-skjerm føles også mye “riktigere” enn 16:10.

Tastaturet er litt uvandt, men det tror jeg nok jeg venner meg til ganske raskt. Lyset i tastaturet er desverre ikke like bra som i Dell’en, men godt nok. Det eneste jeg irriterer meg over er at Lenovoen’s funksjonstast har tatt plassen til venstre Ctrl – kontroll-tasten er ikke under shift som jeg er vandt med, og det er unødvendig knotete å treffe den.

Ubuntu

Å installere Ubuntu 12.04 (64 bit) var smertefritt og gikk raskt. På få minutter koblet jeg opp mine dropbox-, twitter-, facebook- og google-kontoer, og følte meg raskt hjemme. Å installere nye programmer i Linux er utrolig smooth, og Ubuntu har virkelig tatt steget opp og blitt et bra, pent og ikke minst brukervennlig operativsystem. Det nye menysystemet er godt gjennomtenkt, og de virtuelle desktopene fungerer bedre enn alt jeg har forsøkt å få til i Windows.

Det største problemet sålangt dukket opp da jeg forsøkte å koble mine to eksterne skjermer til Lenovoen via dockingstasjonen. Ubuntu klarte ikke å finne dem! Jeg har ikke gjort noe enda for å forsøke å fikse problemet – NVIDIA-skjermkort er et typisk problemområde i Linux-verden, men det skal la seg løse.

Linux i et Windows-miljø

Som utvikler i en bedrift og på et team som bruker Microsoft-teknologi til det aller meste er det en del ting jeg må få til å virke for at Linux ikke skal bli et problem.

For eksempel må jeg kunne “remote” inn til en Windows box. Til dette kommer Ubuntu med Remmina Remote Desktop Client, og den fungerte utmerket. Men jeg må også kunne logge meg inn på vårt Citrix-miljø, og det har jeg ikke fått til enda. Det finnes heldigvis Citrix-klienter for Linux, men så langt jeg har sett er det litt komplisert å få installert dette, så jeg har ikke gjort noe forsøk på dette enda (tips mottas med takk).

En annen ting jeg har installert er Wine. Det er et program som lar deg kjøre Windows-programmer på Linux. Jeg brukte det til å kjøre Windows-klienten til Spotify, og det fungerte utmerket på første forsøk.

Jeg kommer ikke til å slippe helt unna Windows. På sikt håper jeg å kunne fase ut Visual Studio, men inntil videre er det et nødvendig verktøy. Derfor planlegger jeg å kjøre en Windows virtuelt i Linux vha. VirtualBox. Det er derfor jeg vil ha 16 GB RAM. Første forsøk på å installere 64bits Win7 i VirtualBox ble misslykket, men jeg trenger bare litt mer tid!

Andre ting jeg har installert

Ubuntu kommer installert med Firefox, men som Google fanboy har jeg selvfølgelig byttet til Chromium, open-source utgaven av Chrome. Etter å ha logget på med google-brukeren min fikk den tak i mitt vanlige Chrome-tema, bokmerkene og historikken min, og da var det lett å glemme at jeg faktisk befant meg på et ny-installert OS.

Paint.NET erstattet jeg med Gimp, som jo er mye kraftigere – nesten en fullgod erstatning av PhotoShop. Og InkScape, som jeg er en flittig bruker av, finnes også for Linux, så den har jeg også installert.

Jeg benyttet også sjangsen til å teste ut en ny twitter-klient, og fant en som heter Hotot som virker lovende. For annen chat’ing, først og fremst en erstatning for Live Messenger, har jeg installert Pidgin.

Jeg har vært en flittig bruker av Windows Live Writer – jeg har brukt programmet til å skrive blogpostene mine, og håpte jeg kunne finne noe like bra til Linux. Denne blogposten skriver jeg i et program som heter Blogilo, og det ser ut til å ha omtrent den samme funksjonaliteten som Live Writer. Og hvis du leser dette nå så virket WordPress-integrasjonen også som den skulle :)

Git hører jo hjemme i Linux, og kommandolinjen fungerer bedre her enn hva jeg har fått til i Windows. I tillegg har jeg testet ut noen grafiske klienter: gitk og giggle. Jeg kan ikke gi dem noen dom enda…

Ellers har jeg selvsagt installer GVim, og har allerede gjort endel “tweaks” i terminalvinduet. UNIX-kunnskapen fra Universitetstiden begynner å komme tilbake. Jeg har ikke hentet ned Clojure, Ruby, Erlang, Mono eller noen andre utviklingsmiljø enda, men det kommer nok raskt skal du se.

Konklusjon

Den nye laptopen fra Lenovo virker lovende, og Ubuntu er herlig! Jeg har hatt enkelte problemer, men jeg har bare ikke hatt tid til å løse dem enda. Jeg er optimist, og tror alt lar seg løse i Linux :)

Hvilken laptop?

Monday, June 18th, 2012
20 kommentarer

Laptopen min, en DELL Precision M4400, har nå blitt tre år gammel. Batteriet tappes på minutter, den går varm for et godt ord – lakken flasser av på alt av metall, og bråket fra viften er direkte pinlig. Det kan tenkes jeg kan tyne litt mer tid ut av den ved å få meg en SSD drive, men helt ærlig er det nok på tide å begynne å lete etter en erstatning.

Hva jeg skal gå for viser seg derimot å bli et vanskelig spørsmål. Laptopen er mitt viktigste verktøy, så det er ikke vanskelig å argumentere for at man må kjøpe det beste man kan finne. Men hva er det beste?

WP_000625

Min nye laptop må selvfølgelig ha en førsteklasses CPU: Intel Core i7 med 8 MB hurtigbuffer og fire kjerner burde være bra. SSD er “must have”, men behøver ikke være så veldig stor, 250 burde holde for meg.

Jeg har 8 GB minne i dagens maskin, og mange sier det er nok. Jeg tenker derimot at om jeg ønsker å bruke virtuelle maskiner i utstrakt grad så bør jeg ha mer – 12 eller 16.

Men å stappe maskinen full av hardware er ikke bare positivt; jeg er også opptatt av at den ikke bør være for tung og uhåndterbar. Jeg er heller ikke interessert i en maskin med fullt tastatur – altså med numpad.

Angående skjermstørrelse tenker jeg 15 tommer, men skjermen bør være bedre enn den jeg har i dag. Moderne skjermer kan brukes i sollys, og noen har flotte glass som du kun kan skade med diamanter. Er jeg villig til å gå for 13 tommer får jeg også velge mellom noen veldig lette og tynne maskiner.

Sist men ikke minst har jeg lyst til at den skal se bra ut. Jeg tror følelsen av å jobbe på et vakkert redskap betyr mer enn man er klar over. Kall meg gjerne forfengelig (det er ikke så ofte noen har gjort det (: ).

Hvilket operativsystem?

I dag bruker jeg Windows 7, men jeg ser ikke frem mot å oppgradere til Windows 8. Det kan være strategisk lurt av meg å bli kjent med Mac OS X – vi kan dra nytte av det på jobben. Om jeg ønsker å kunne utvikle for Mac så bør jeg ha en Mac, og kjøre Windows virtuelt.

Men egentlig har jeg mest lyst til å kjøre Linux som primært OS. Siste versjon av Ubuntu og Unity er skikkelig slick. Eventuelt er det jo også mulig å kjøre en løsning hvor man kun kjører virtuelle OS’er, og enkelt kan swappe mellom dem. VMWare har en sånn løsning tror jeg.

Hvilket merke?

I PSWinCom kjøper utviklerne utstyret sitt fra DELL. En av fordelene er blant annet at alle har de samme dockingstasjonene, men for å være ærlig vet jeg ikke hvor mye akkurat det betyr. Jeg har skikket litt rundt, og det virker på meg som om andre merker har innovert mer enn det DELL har gjort.

Apple’s MacBook Pro med retina-skjerm, som ble lansert forrige uke, er noe jeg har siklet litt etter. Den skårer svært høyt i forhold til “kravene” mine. Men skal jeg virkelig gå for Mac? Blir overgangen for stor? Jeg vet ikke engang om jeg kan kjøre to eksterne ikke-Apple skjermed med Pro’en. Og bør jeg ikke holde meg langt unna den aller første laptopen med retina-skjerm – den kan ha ukjente problemer, og det er ikke sikkert opplevelsen blir så bra når man bruker programmer som ikke er “retinaklargjort”.

Av andre merker har jeg hørt mye bra om Lenovo, men synes ikke det jeg har sett ser så veldig fristene ut. HP ser derimot ut til å ha komt ganske langt i forhold til sist jeg brukte en. ASUS ZENBOOK er fin og praktisk – om jeg er villig til å gå for en litt mindre skjerm og mindre minne. Men det er jeg vel ikke :)

Acer sine maskiner er heller ikke så dumme. Og så har vi Sony VAIO, kanskje det aller stiligste. Igjen snakker vi litt mindre skjerm, men ellers er den toppspecket.

Arrg, alt for mye å sette seg inn i og alt for mye å velge blant. Det virker som om man kan få omtrent samme specken fra de fleste, men alle merkene har hver sine fordeler. Og de fleste er mere tiltalende enn DELL :P

Hva skal jeg velge? Hvordan skal jeg velge? Jeg er ikke en hardware-mann, så vær så snill, gi meg råd!!

AutoIt

Friday, December 2nd, 2011
3 kommentarer

Er du lat, og vil at noen gjør alle de kjedelige og repetetive oppgavene for deg? Hvis du i tillegg befinner deg på Microsoft’s Windows-plattform bør du i såfall ta en titt på automasjonsspråket AutoIt!

autoit

AutoIt er et BASIC-lignende språk med bibloteker og verktøy for å lage makroer. Gjennom kode kan du enkelt bevege og klikke med musen, starte programmer, sende tastetrykk fra tastaturet, og generelt simulere og automatisere alt det du kan gjøre som en bruker i Windows.

AutoIt-kodefiler kan normalt kjøres ved dobbeltklikk på maskiner som har AutoIt installert. I tillegg kan skriptene kompileres til en komprimert, frittstående, eksekverbar fil som kan kjøres på andre maskiner.

La oss se på litt kode

La oss igjen lage en løsning på Eulerproblem nummer 1, hvor vi skal finne summen av alle tall under 1000 som er multipler av enten 3 eller 5. Jeg bruker den imperative løsningsmodellen, og har valgt å splitte det opp i to separate funksjoner – en for å finne ut om et tall skal inkluderes, og en funksjon med en løkke som summerer opp tallene.

10 Func MultipleOf3Or5($value)
11     Return Mod($value, 3) == 0
12         OR Mod($value, 5) == 0
13 EndFunc
14 
15 Func SolveEuler1()
16     Dim $sum = 0
17     For $i = 1 to 999
18         If MultipleOf3Or5($i) Then
19             $sum += $i
20         EndIf
21     Next
22     Return $sum
23 EndFunc

Ikke spesielt spennende dette her. Men nå kommer rosinen i pølsa..

For å skrive ut resultatet vil jeg benytte noen av de tingene som AutoIt egner seg best til, nemlig å manipulere vinduer. I koden nedenfor åpner jeg først Notepad, skriver ut svaret i vinduet, og lukker så programmet.

Når Notepad lukkes vil det dukke opp en advarsel om at jeg har endringer som ikke er lagret. Det er jeg forberedt på, og sender tastekombinasjonen Alt+S, som trigger lagre-dialogen. Der skriver jeg inn stien jeg ønsker å lagre svaret mitt til, og sender igjen Alt+S som fullfører lagringen, og Notepad avsluttes.

25 Run("notepad")
26 Dim $answer = SolveEuler1()
27 WinWaitActive("Untitled - Notepad")
28 Send("The answer is " & $answer & "{ENTER}")
29 
30 ; Provoke unsaved changes dialog, click "Save"
31 WinClose("Untitled - Notepad")
32 WinWaitActive("Notepad", "Save")
33 Send("!s") ; Alt+S for "Save"
34 
35 ; Enter save location in file dialog, click "Save"
36 WinWaitActive("Save As")
37 Send(@MyDocumentsDir & "\EulerProblem1_Answer.txt")
38 Send("!s") ; Alt+S

Når jeg kjører dette skriptet vil jeg se at Notepad dukker opp, at svaret tastes inn, og at skriptet går gjennom lagre-dialogene. Til slutt er svaret lagret i My Documents-katalogen min.

Hvorfor bruke tid på AutoIt?

AutoIt er ikke et interessant språk i seg selv. Men for automatisering av Windows er det perfekt, og et slikt verktøy burde alle utviklere på den plattformen beherske.

Mange anbefaler også AutoIt som et bra sted å begynne å lære seg programmering. BASIC-syntaksen er enkel å forholde seg til, og den direkte interaksjonen mot brukergrensesnittet kan være motiverende og inspirerende i starten.

Svakheter

AutoIt er en-trået. Dette gjør det vanskelig (men ikke umulig) å programmere asynkrone og paralelle ting.

Personlig synes jeg dessuten at BASIC-lignende språk er veldig slitsomme – dette til tross for at jeg først og fremst var en Visual Basic-programmerer for noen år tilbake, og til tross for at mitt aller første språk var en BASIC-dialekt, nemlig AMOS på Amiga.

Et annet språk/verktøy du kan vurdere er AutoHotKey. I stedet for å fokusere på å simulere brukere og automatisere vinduer er AutoHotKey først og fremst laget for å knytte makroer til spesielle tastekombinasjoner. Selve AutoHotKey-språket er litt mer kryptisk enn AutoIt.

Hvordan komme i gang?

Du finner AutoIt på autoitscript.com. Når du installerer det får du i tillegg et utviklingsmiljø basert på SciTE-editoren. Her er kompilatoren integrert, og man har tilgang til hjelpefiler med den informasjon man trenger for å begynne å lære språket og verktøyet, og for å lage scrips som utfører de aller fleste oppgaver.

JavaScript revolution

Friday, October 21st, 2011
1 kommentar

JavaScript har i løpet av 15 år gått fra å være et forferdelig mareritt til å bli en av de mest spennende og pulserende teknologiene man kan holde på med. Det skjer så mye rundt JavaScript for tiden at det rett å slett er vanskelig å henge med.

Denne artikkelen er et forsøk på å skape en oversikt over viktige open-source biblotek det kan være nyttig å vite om. I takt med modningen av HTML5 har jeg blitt mer og mer interessert i JavaScript-utvikling det siste året. Denne listen inneholder noen ting jeg har testet ut, men vel så mange ting jeg ikke har jobbet med, men ønsker å få sett nærmere på i tiden fremover.

Finner du noen åpenbare mangler i listen min – spennende biblotek eller rammeverk jeg har utelatt – så må du slenge inn en kommentar!

De generelle bibloteken

Først og fremst bør alle webutviklere kjenne til det jeg tenker på som de store, generelle JavaScript/Ajax-biblotekene. Jeg tenker da på Prototype, DOJO, Moo Tools, YUI Library, og sist men ikke minst jQuery, som er det som nå er aller mest populært. Siden jeg bruker jQuery selv, ser jeg ingen åpenbar grunn til å se på de andre.

jQuery har som kjent også et hav av plugins for alt mulig man kan komme til å trenge. Det kan være viktig å lete litt blant dem før man begynner å lage noe selv.

JavaScript også utenfor browseren

JavaScript er et kraftig språk med noen av de raskeste, dynamiske tolkerne (interpreters) som finnes. Og det brukes til langt mer enn å bare lage websider. Node.js er det mest populære rammeverket som tar JavaScript server-side, og er basert på Google’s V8-motor som kjører i Chrome. Jeg har lekt meg litt med Node tidligere, og synes det er veldig spennende.

CommonJS er et initiativ som skal definere et standard API for JavaScript utover det som språket selv definerer. Fokuset her er nettopp på det å bruke JavaScript utenfor browseren – til å lage server-applikasjoner, kommandolinje-verktøy, desktop GUI-applikasjoner, og hybridapplikasjoner i f.eks. Titanium og Adobe AIR. Titanium har jeg også testet litt, og det er et veldig spennende alternativ hvor du lager programmer som kjører på Windows/Mac/Linux ved å bruke HTML og CSS pluss en valgfri kombo av JavaScript, Ruby og Python.

Når jeg sier JavaScript så mener jeg forresten egentlig CoffeeScript. Dette er en rykende ferskt språk som kompileres ned til JavaScript. Man kan bruke alt som finnes av biblotek fra JavaScript-verdenen, men koder selv i en mye mere behagelig syntaks. Jeg har allerede konvertert teamet mitt på jobben til å bruke CoffeeScript fremfor JavaScript, og anbefaler deg til å vurdere det samme.

Noen sentrale byggeklosser

RequireJS laster JavaScript-filer og moduler. Å bruke dette vil gjøre løsningen din både raskere og enklere å holde oversiktelig etterhvert som den vokser seg større. RequireJS gjør det enklere for deg som utvikler å lage større JavaScript-løsninger, samtidig som den optimaliserer selve deploymenten av løsningen.

Underscore.js er en verktøykasse som gir JavaScript bedre støtte for funskjonell programmering. Her finner du rundt 60 generelle funksjoner som each, map, select, reduce, range osv.

HTML5_Logo_128

Modernizr er et biblotek som skal hjelpe deg å bygge neste generasjons HTML5- og CSS3-løsninger som også vil virke for dem som bruker browsere som ikke støtter de nye featurene. Dvs., Modernizr utfører ikke mirakler – det får ikke disse nye tingene til å virke i gamle browsere – men det forteller deg hva som virker, slik at du kan ta høyde for det og kode deretter.

WebSockets har potensialet til å revolusjonere hvordan vi lager rike webapplikasjoner, og er noe av det aller mest spennende som ligger i HTML5-standarden (en bra presentasjon om dette). Socket.IO er et biblotek som skal gi deg realtime apps, uavhengig av hvilken browser eller mobil-device brukeren har. Det bruker WebSockets om det er tilgjengelig, og faller tilbake på andre løsninger om det ikke er det. Socket.IO kan også brukes på server-siden i f.eks. Node.js.

Og så må jeg få nevne Date.js, et lite biblotek som lar deg jobbe med datoer i JavaScript på en enkel måte.

MVC-aktige rammeverk

Nå har vi kommet til de rammeverkene som skal legge til rette for å lage JavaScript-tunge webløsninger. Jeg tenker da spesielt på såkalte single-page JavaScript applications. GMail er et typisk eksempel på en sånn app.

Backbone.js gir deg (meget kort fortalt) muligheten til å lage datamodeller, definere views, og å knytte disse sammen med en toveis databinding. Backbone.js bruker også Underscore.js for å tilby et rikt API.

Sammy.js er et lite men veldig spennende rammeverk som lar deg designet single-page JavaScript apps etter en restfull tankegang med ruter (routing). Du kan bruke ulike template/rendering-motorer, og Sammy føles både enkel og elegant.

Brunch beskrives også som et lettvekts rammeverk for å lage HTML5-applikasjoner med fokus på eleganse og enkelhet. Det kombinerer CoffeeScript, Backbone.js, Underscore.js, jQuery og flere andre biblotek til en pakke for å lage MVC-lignende løsninger.

Andre rammeverk jeg har fått med meg som er populære, men som virker noe større (men ikke dårligere av den grunn) inkluderer SproutCore, Cappuccino og Sencha. For rendering / templates finnes det enda flere muligheter, som Eco, Stitch, mustache, EJS, pure og meld. Ett av disse bør man nok lære seg å bruke.

Grafikk og effekter

Det finnes mange, gode biblotek for å lage ulike typer grafer, men jeg orker ikke liste dem her. Det finnes også flere biblotek som fokuserer på å tilby ulike JavaScript-effekter, som f.eks. Script.aculo.us.

$fx() er et annet biblotek for å animere HTML-elementer. Med bare 4k JavaScript lar det deg endre CSS-properties over en tidslinje på en ganske så elegant måte. Er du kreativ kan du lage noen veldig spennende websider med dette kompakte scriptet.

Er du en av dem som behersker 3D-grafikk er WebGL noe du bør se på. Ved å bruke canvas-elementet i HTML5 gir det deg 3D rett i browseren, uten å bruke plug-ins. WebGL er basert på OpenGL.

Og er du helt sprø så lærer du deg Processing.js. Processing er et programmeringsspråk og en utviklingsplattform for å lage elektronisk kunst og visuelt design, og Processing.js er en portering av dette til JavaScript. Ta en titt på linken for å se hva du kan gjøre med det.

Testdreven JavaScript

Når det kommer til testing så virker det som om det mest populære rammeverket en stund har vært QUnit (det er det jQuery bruker). Et alternativ som bruker begreper som er mer kjente for dem som driver med BDD er Jasmine. I tillegg kan det være nyttig å bruke Sinon.JS for mocking, og jsTestDriver for å kjøre testene.

Buster.JS er et annet testrammeverk som det snakkes mye om, men som ikke har nådd noen release enda. Det virker som det vil være kompatibelt med QUnit, bruke Sinon.JS for mocking, ha BDD-syntax for dem som ønsker det, og ha støtte for det samme som man finner i jsTestDriver. Her blir det altså en mere komplett løsning i én pakke.

Headless browser

Helt til slutt vil jeg nevne PhantomJS. Dette er et kommandolinje-verktøy som inneholder en “usynlig” web-browser. Man kan gjøre alt man kan gjøre med en vanlig browser, og styre den med JavaScript. Dette egner seg veldig godt til ting som testing av webapplikasjoner, “webskraping”, og mye, mye mer om du har litt fantasi.

Mye å sette klærne i her altså. Har du noen erfaring med disse biblotekene som du vil dele?

Pomodoro

Saturday, October 8th, 2011
1 kommentar

POMODORO1

Pomodoro er en teknikk for å administrere sin egen tid mens man jobber, en teknikk som skal hjelpe deg å fokusere på det du holder på med, og gjøre deg mer effektiv.

Det fungerer slik at man jobber i sprinter a 25 minutter om gangen – de kalles “pomodori”, med en fem minutters tvungen pause mellom hver. Etter fire pomodori tar man en noe lengre pause.

Når man jobber skal man være helt fokusert. Hvis man blir forstyrret er det viktig at man håndterer dette riktig. Typiske forstyrrelser er at man selv begynner å tenke på noe urelatert til oppgaven man holder på med. Da skal man raskt skrive ned hva man tenkte, slik at man kan “glemme det” og fortsette på oppgaven.

Jeg ble introdusert for Pomodoro på QCon-konferansen i London i 2010, og har siden den gang hatt en intensjon om å prøve det ut. For noen uker siden fant jeg et ganske bra Adobe Air-basert verktøy for å holde track på prosessen som het Pomodairo. Og nå har jeg endelig fått testet ut Pomodoro-teknikken over litt tid.

Det finnes mange slike Pomodoro-vertøy, men dette er nok det beste jeg har funnet. I tillegg til å holde track på tiden, og gi alarm når man skal starte og stoppe å jobbe, så lar det deg også legge inn oppgavene man jobber på. Man kan estimere hvor mange pomodori man tror man vil bruke på hver oppgave, og man kan enkelt registrere inn forstyrrelsene underveis. Til slutt gir Pomodairo deg oversikt over hvordan det har gått.

POMODORO2

Her er rapporten som viser hvordan de ulike dagene har gått. Som du ser får jeg en delta som viser avviket mellom estimat og faktisk antall pomodori, og antall forstyrrelser hver dag.

POMODORO3

Og i dette skjermbildet kan jeg søke i og analysere alle forstyrrelsene.

Etter å ha forsøkt pomodoro i et par uker føler jeg at dette er noe jeg bør fortsettet med – i alle fall deler av tiden. Under pomodoriene jobbet jeg intenst, og med veldig høyt fokus. I begynnelsen opplevde jeg også litt stress, fordi jeg jobbet med en klokke som telte ned, og forsøkte å bli “ferdig” med ting før klokken ringte. Denne intensiteten og stresset bør nok kontres med perioder hvor jeg ikke bruker teknikken. Av og til er det viktig å slappe av og bare la tankene vandre.

Det var også noen ganger litt vanskelig å faktisk ta den obligatoriske pausen, når jeg var midt oppi noe spennende.

Jeg sitter og jobber i landskap med hele teamet rundt meg. Dette fører til veldig god kommunikasjon og kunnskapsdeling, men er også utfordrende i forhold til fokus. Det mest positive med pomodoro er at man blir veldig bevisst på avbrytelser, både eksterne og interne. Ikke bare slutter man å sjekke e-post og twitter i tide og utide, men man klarer etterhvert å blokkere ut både urelevante tanker og ekstern støy som ellers ville ha forstyrrer.

Etter et par uker med Pomodoro føler jeg at jeg har blitt litt bedre på å holde fokuset oppe over lengre tid om gangen. Jeg anbefaler alle å forsøke!

Til slutt vil jeg bare nevne at Pomodoro også skal egne seg bra for parprogrammering. Når jeg jobber i par føler jeg skjelden at det er noe problem å holde fokuset oppe, men da er det kanskje viktigere at man får de obligatoriske pausene, slik at man ikke blir helt utslitt.

Hvis du har noen erfaringer med bruk av Pomodoro, enten det er i par eller solo, så er jeg veldig interessert i å høre om det.

Git

Friday, February 11th, 2011
1 kommentar

git-logoI dag har jeg holdt ziptalk om Git – det raske versjonskontrollsystemet. Jeg har brukt git litt på privaten, spesielt opp mot Github da selvfølgelig, men nå følte jeg det var på tide å lære det orntlig, og å se om det kunne gi teamet vårt noen fordeler. Vi bruker subversion i dag, men har behov for å bruke branching mer effektivt – i forhold til hyppigere deployment – og jeg tror Git kan være veien å gå.

Linus Torvalds has quipped about the name “git”, which is British English slang for a stupid or unpleasant person: “I’m an egotistical bastard, and I name all my projects after myself. First Linux, now git.”

The official Git wiki also gives a number of alternative explanations and backronyms for the name, including “Global Information Tracker”.

Ziptalken var basert på det tutorialet du finner på gitref.org, og anbefales til alle som trenger en introduksjon. Ellers finnes det en gratis online-bok som virker grei på progit.org, og git wikien en selvfølgelig også en naturlig referanse..

Ellers må jeg nevne at jeg er en typisk kommandolinje-fyr, og har stort sett brukt git på den måten. Men i går installerte jeg litt porselen i form av Git Extensions for Windows, og sålang må jeg si at jeg er imponert. Denne integrasjonspakken bør gjøre git spiselig for de fleste.

Implementere toveis SMS i .Net

Wednesday, June 16th, 2010
2 kommentarer

At en tjeneste benytter toveis SMS vil si at den både kan motta meldinger fra brukere (mobile originated, MO), og at den kan sende meldinger til brukere (mobile terminated, MT). Å lage en slik tjenste som bruker PSWinCom’s SMS gateway er veldig enkelt, takket være gode integrasjonsgrensesnitt. I denne artikkelen vil jeg implementere en slik tjeneste i .NET / C#.

Jeg registrerer meg hele tiden på ulike nettsteder, som alle ber meg om å spesifisere et passord. Og da er det veldig dumt å bruke det samme passordet hele tiden. Jeg ønsker derfor å kunne sende kodeord PASSORD til et bestemt telefonnummer, og få tilbake et garantert tilfeldig passord jeg kan bruke. Denne tjenesten er altså tiltenkt sikkerhetsfanatikere med dårlig fantasi.

toveis_sms

For å lage tjenesten vil jeg implementere en web service som jeg kan hoste i IIS. Tjenesten har to integrasjonspunkt: Ett SOAP-endepunkt må implementeres for å motta SMS-meldinger fra gateway’en, og i tillegg må tjenesten kalle en tilsvarende SOAP-tjeneste som gateway’en eksponerer når vi ønsker å sende melding tilbake til brukeren som ber om et passord.

For å lage en slik tjeneste trenger jeg produktet Gateway standard (produktark), og PSWinCom må registrere URLen til tjenesten min for kodeordet jeg vil bruke. Det er mulig å få demokonto for dem som ønsker å teste mulighetene. Det kan også være lurt a lage sin egen test-gateway (fakeway) om man vil teste tjenesten uten å nødvendigvis sende og motta SMS’er – det vil komme en egen blogpost om hvordan man enkelt kan gjøre dette.

Grensesnitt for innkommende meldinger

Å bruke SOAP-grensesnittet for mottak av SMS-meldinger i .Net er veldig enkelt. Først starter du Visual Studio Command Prompt, og bruker kommandoen wsdl for å generere et service interface.

Kommando for å generere SMS receive service interfaces:
wsdl /namespace:RandomPasswordService /serverInterface https://secure.pswin.com/SOAP/Receive.asmx?wsdl

Dette vil resultere i en fil som heter SMSReceiveInterfaces.cs. Den inneholder blant annet et interface som heter ISMSReceiveSoap. For å kunne ta imot meldinger må vi lage en web service som implementerer dette interfacet.

Grensesnitt for utgående meldinger

For å sende SMS-meldinger fra passord-tjenesten kan vi bruke et lite bibliotek, PSWinCom SMS Gateway Client for .NET, som kan lastes ned fra pswin.com. Om du ønsker å generere en SOAP proxy selv kan du gjøre det i stedet. All informasjon du trenger finner du i dokumentasjonen over grensesnittene.

RandomPasswordService-prosjektet

Jeg oppretter så et nytt ASP.NET Web Application prosjekt i Visual Studio som skal bli passord-tjenesten vår. Jeg sletter Default.aspx, og legger i stedet til SMSReceiverInterfaces.cs som jeg genererte, samt en referanse til PSWinCom.Gateway.Client.

Deretter legger jeg til en web service jeg kaller Password.asmx. Det er i denne servicen vi skal implementere interfacet for å håndtere innkommende meldinger. Til slutt legger jeg til en ny klasse-fil jeg kaller RandomDotOrg.cs hvor jeg vil implementere generering av passord, og rydder litt opp i dll-referansene.

CropperCapture[72]

Jeg er nå klar til å implementere tjenesten. I web servicen sin code behind sørger jeg først for å endre WebService namespace til http://pswin.com/SOAP/Receive (ikke glem dette!). Deretter sier jeg at Password skal arve fra ISMSReceiveSoap, og får Visual Studio til å generere stubs for metodene jeg trenger. Etter et par minutter er tjenesten ferdig:

    1 using System;
    2 using System.Web.Services;
    3 using PSWinCom.Gateway.Client;
    4 
    5 namespace RandomPasswordService
    6 {
    7     [WebService(Namespace = "http://pswin.com/SOAP/Receive")]
    8     [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    9     [System.ComponentModel.ToolboxItem(false)]
   10     public class Password : WebService, ISMSReceiveSoap
   11     {
   12         #region ISMSReceiveSoap Members
   13 
   14         public ReturnValue ReceiveSMSMessage(IncomingSMSMessage m)
   15         {
   16             try
   17             {
   18                 Send(new Message
   19                 {
   20                     ReceiverNumber = m.SenderNumber, // return to sender
   21                     SenderNumber = m.ReceiverNumber,
   22                     Text = RandomDotOrg.GetNewPassword(),
   23                 });
   24             }
   25             catch { /* Log this !! */ }
   26             return new ReturnValue { Code = 200 };
   27         }
   28 
   29         public ReturnValue ReceiveDeliveryReport(DeliveryReport dr) {
   30             throw new NotImplementedException(); // not interested
   31         }
   32 
   33         public ReturnValue ReceiveMMSMessage(IncomingMMSMessage m) {
   34             throw new NotImplementedException(); // not interested
   35         }
   36         #endregion
   37 
   38         private void Send(Message message)
   39         {
   40             var smsClient = new SMSClient()
   41             {
   42                 Username = "",          // your Gateway username
   43                 Password = "",          // your Gateway passord
   44                 PrimaryGateway = "",    // the Gateway URL
   45                 SecondaryGateway = ""// backup Gatetway URL
   46             };
   47             smsClient.Messages.Add(0, message);
   48             smsClient.SendMessages();
   49         }
   50     }
   51 }

Metoden ReceiveSMSMessage kalles av gatewayen når det ankommer en ny melding. Interfacet har et par metoder til, for å ta imot meldingskvitteringer og MMS-meldinger, men de er jeg ikke interessert i her.

Jeg har laget en egen metode for å sende ut meldinger (linje 38), og det er her jeg bruker PSWinCom.Gateway.Client. Her kan du hente inn brukernavn, passord etc. fra konfigurasjonsfilen eller lignende.

Merk at jeg må returnere statuskode 200 fra ReceiveSMSMessage for å bekrefte at jeg klarte å ta imot meldingen. Hvis jeg ikke gjør dette vil gatewayen forsøke å sende meldingen på nytt (i henhold til definerte retry-regler).

Det er selvsagt mye som kan gjøres i forhold til feilhåndtering etc. Jeg ville normalt også inkludert endel logging. I tillegg kunne jeg ha benyttet meg av informasjonen i den innkommende meldingen, som hvilket kodeord som ble benyttet, eller hva den øvrige meldingsteksten var. Jeg kunne for eksempel lagt opp en mulighet for at brukerne kunne spesifisere hvor langt passordet skulle være ved å sende PASSORD n, hvor n er antall tegn.

Random passord

Det eneste som gjenstår er å implementere generering av passord. Til det vil jeg bruke en tjeneste som heter random.org, som gir meg ekte tilfeldighet, noe jeg ikke kan få til selv på windows. Her er GetNewPassord, som gjør et web-kall til random.org for å hente en random streng á 8 tegn.

    1 using System.Net;
    2 
    3 namespace RandomPasswordService
    4 {
    5     public static class RandomDotOrg
    6     {
    7         public static string GetNewPassword()
    8         {
    9             using (var webClient = new WebClient())
   10             {
   11                 return webClient
   12                     .DownloadString(
   13                     "http://www.random.org/strings/?" +
   14                     "num=1&len=8&digits=on&upperalpha=on" +
   15                     "&loweralpha=on&unique=on&format=plain&rnd=new")
   16                     .Trim();
   17             }
   18         }
   19     }
   20 }

(Merk at random.org har en quota per IP-adresse. Les guidelines for automated clients om du ønsker å benytte deg av random.org.)

Så der har du det altså, en komplett, toveis SMS-tjeneste i .Net / C# (som i tillegg kommuniserer med en ekstern tjeneste) på 71 linjer. Så hvis du har en god idé til en SMS-tjeneste bør du i alle fall ikke la kompleksiteten stopp deg, for så veldig mye enklere blir det ikke. 

Som nevnt skal jeg også skrive en artikkel om hvordan du kan teste tjenesten din uten å bruke PSWinCom’s gateway, så følg med om du er interessert i det.

Relatert artikkel: Dagens sitat via SMS (énveis SMS m/Ruby og XML over TCP).

Avhengighetsisolering (a.k.a. Mocking) i .NET

Thursday, August 27th, 2009
4 kommentarer

mocking_frameworks I min forrige blogpost, om utenfra-og-inn programmering, snakket jeg om at mocking er en teknikk vi bruker når vi praktiserer testdreven utvikling. For å kunne utvikle én og én enhet om gangen er det fordelaktig å kunne erstatte enhetens avhengigheter med “jukseobjekter”.

Roy_Pic_BW_Small Enhetstest-guru Roy Osherove (som jobber for TypeMock) påpeker at vi ofte misbruker begrepet mocking. Martin Fowler har også skrevet en mye lest artikkel kalt Mocks Aren’t Stubs om dette: En MOCK er ifølge dem et objekt som brukes til å registrere og validere forventninger, mens en STUB er et objekt som inneholer predefinerte svar. FAKE er et objekt med en faktisk implementasjon, som for eksempel en in-memory database som erstatter en faktisk database. Vi bruker normalt en blanding av disse i testene våre, og jeg ser ikke noen spesiell grunn til å kunne skille mellom dem.

Roy anbefaler å bruke begrepet avhengighetsisolering for å samle disse teknikkene, og kaller TypeMock for et avhengighetsisoleringsrammeverk (det ble veldig fint på norsk gitt). Jeg bruker likevel mocking-begrepet, siden det er mest kjent – alle forstår hva jeg mener da.

Uansett hva man kaller dem – fordelen med disse falske objektene er at de er til å stole på. Mens faktiske implementasjoner kan gi ulike svar (basert på eksterne data, randomness etc.) kan vi fullstendig kontrollere hvordan en fake skal oppføre seg i enhver situasjon. For eksempel kan vi simulere ulike unntasksituasjoner ved å få dem til å kaste exceptions på kommando.

Testene blir i mange tilfeller også enklere å skrive, gitt at du behersker et godt mockingrammeverk. Testene kan dessuten eksekvere raskere, fordi avhengighetene blir “lettere” enn de faktiske avhengighetene.

Litt kontroverielt

Jeg føler meg forpliktet til å nevne at det er en viss diskusjon rundt bruk av mocking. Det er mange som hevder at mocking er unødvendig, og kanskje til og med uønsket, om man designer systemet sitt “riktig”. Jeg var selv av den oppfatning for ikke så alt for lenge siden. Men etter å ha forsøkt det en stund endret jeg mening.

Likevel innser jeg at mocking kan missbrukes – spesielt de kraftigste rammeverkene. Når man skriver tester er det viktig å teste adferd, ikke implementasjon – knytter man dem for hardt mot implementasjonen blir testene sårbare for endringer i systemet. Du kan få en grei oversikt over denne debatten ved for eksempel å lese et par sentrale spørsmål på stackoverflow: Why do I need a mocking framework for my unittests? | The value of high level unit tests and mock objects | Should one test internal implementation, or only test public behaviour?

Resten av denne blogposten er ment å gi en oversikt over hvilke muligheter du som utvikler på .NET-plattformen har for å praktisere avhengighetsisolering.

Manuell mocking

La oss først se på hvordan manuell gjør-det-selv-mocking fungerer. Det kan være lurt å starte der, for å få en grunnleggende forståelse for hvordan og hvorfor man mocker. La oss si at jeg skal teste en LoginController-klasse. Den har to avhengigheter; et LoginView og en UserRepository.

    1 [Test]

    2 public void Should_login_user_successfully()

    3 {

    4     var view = new FakeLoginView();

    5     new LoginController(view, new FakeUserRepository());

    6     view.TriggerAuthenticationRequested();

    7     Assert.IsTrue(view.hidden);

    8     Assert.IsTrue(view.Result == AuthenticationResult.Ok);

    9 }

Etter best practises injectes avhengighetene inn gjennom konstruktøren til kontrolleren – dette gjør det mulig for oss å mocke avhengighetene gjennom arv. I testen oppretter jeg stubs som jeg bruker til å simulere at en bruker trigger en authentiserings-request, og til å sjekke at resultatet av authentiseringen er at viewet skjules og Result-propertien settes til OK.

De to stub-klassene, som jeg koder spesifikt for denne testen, ser slik ut:

    1 class FakeLoginView : LoginView

    2 {

    3     // LoginView Members

    4     public event EventHandler AuthenticationRequested;

    5     public string Username { get { return “torbjorn”; } }

    6     public string Password { get { return “passw0rd”; } }

    7     public AuthenticationResult Result { get; set; }

    8     public void Hide() { hidden = true; }

    9     // end LoginView Members

   10 

   11     public FakeLoginView() { Result = AuthenticationResult.Pending; }

   12     public virtual void TriggerAuthenticationRequested()

   13     {

   14         if (AuthenticationRequested != null)

   15             AuthenticationRequested(null, EventArgs.Empty);

   16     }

   17     public bool hidden;

   18 }

   19 

   20 class FakeUserRepository : UserRepository

   21 {

   22     public User FindByName(string username)

   23     {

   24         return new User {

   25             PasswordHash = “BED128365216C019988915ED3ADD75FB” };

   26     }

   27 }

Stub’ene er spesialdesignet for å gi den adferden jeg trenger i testen; FakeLoginView returnerer et predefinert brukenavn og passord, og har en variabel jeg kan sjekke mot for å finne ut om Hide-metoden har blitt kalt. FakeUserRepository vil uansett input returnere en bruker med et passord-hash som korresponderer med det predefinerte passordet.

Det er endel arbeid å lage disse stub’ene, men det er ikke så vanskelig. Problemet er at slik de er nå er de bare anvendelige for et fåtall tester – for andre tester som skal simulere andre ting må jeg skrive nye stubs (eller utvide disse med mer logikk). Jo flere stubs man må lage, jo mer kompleksitet innfører man i test-suiten.

Moq

Moq er et relativt nytt, open source mockingrammeverk for .NET, utviklet av Daniel Cazzulino (@kzu). Rammeverket er designet med tanke på enkelhet, er basert på Castle DynamicProxy, og er min klare favoritt. Jeg har nylig innført Moq på jobben, og det virket ikke som om utviklerne hadde store problemer med å ta det i bruk.

Moq bruker ikke Record/Replay-semantikken som flere andre rammeverk er kjent for, det har en mere intuitiv Arrange-Act-Assert approach. Eneste potensielle ulempen med rammeverket er at du må bruke .NET 3.5 (eller høyere).

Tilsvarende test som den over blir som følger ved bruk av moq:

    1 [Test]

    2 public void Should_login_user_successfully()

    3 {

    4     var view = new Mock<LoginView>();

    5     view

    6         .Setup(v => v.Password)

    7         .Returns(“passw0rd”);

    8 

    9     var repository = new Mock<UserRepository>();

   10     repository

   11         .Setup(r => r.FindByName(It.IsAny<string>()))

   12         .Returns(new User { PasswordHash = “BED128365216C019988915ED3ADD75FB” });

   13 

   14     new LoginController(view.Object, repository.Object);

   15     view.Raise(v => v.AuthenticationRequested += null, EventArgs.Empty);

   16     view.Verify(v => v.Hide());

   17     view.VerifySet(v => v.Result = AuthenticationResult.Ok);

   18 }

I linje 4 til 12 setter jeg opp mockene (Arrange): Jeg lager et view som skal returnere et predefinert passord, og en repository som skal returnere et predefinert passord-hash. I linje 14 sender jeg inn mockene til controlleren, og i linje 15 trigger jeg autentiseringforespørselen (Act). I linje 16 verifiserer jeg at Hide-metoden har blitt kalt, og linje 17 sjekker at Result har blitt satt til OK (Assert).

Moq er under stadig utvikling, og virker ganske populært for tiden. Daniel lanserte for eksempel nettopp Linq to Mocks – en helt ny approach for å opprette mock-objekter i form av en spørring. Det eksisterer også et moq-contrib prosjekt hvor du finner støtte for Automocking (rekursiv oppretting av mocks – innebærer mindre Arrange-kode i testene). OPPDATERING: Rekursiv mocking har blitt flyttet fra moq-contrib til å bli del av moq i versjon 2.6.

TypeMock Isolator

TypeMock Isolator er det aller kraftigste mockingrammeverket vi har tilgjengelig – det kan gjøre ting ingen av de andre kan, som å mocke ikke-virtuelle metoder, statiske klasser og klasser merket som sealed. Ulempen er at det koster penger.., Ganske Mange Penger!

Mange hevder også at styrken dette rammeverket tilbyr lett kan føre til dårligere design, og det er jeg enig i. Vi tok i bruk TypeMock i min forrige jobb, og der førte det til at vi lagde dårlige og komplekse tester som var mer eller mindre umulige å vedlikeholde. Når man derimot allerede har et dårlig design som utgangspunkt – kanskje man har behov for å mocke deler av selve .net-rammeverket – så kan nok TypeMock være verdt sin vekt i gull. (Men hvor mye veier software egentlig?)

Rhino Mocks

Rhino Mocks er et open source rammeverk utviklet av Oren Eini (også kalt Ayende Rahien). Mens TypeMock er det kommersielle storebror med de kraftigste featurene, er Rhino Mocks det mest brukte mockingrammeverket blant .NET-utviklere. Tidligere hadde Rhino en lite intuitiv Record/Replay-syntax, men støtter nå Arrange-Act-Assert, og da ligner det i grunnen svært mye på Moq.

Rhino Mocks må sies å være et svært modent og fleksibelt rammeverk, og som Moq bygger det på Castle DynamicProxy. Den store fleksibiliteten kan være forvirrende, og dokumentasjonen kan være mangelfull. Dette veies opp ved at det har svært mange brukere, så noen andre har alltid gjort det du skal gjøre, og sansynligvis blogget om det.

Flere har utvidet Rhino Mocks til også å støtte automocking, bl.a. Jeremy D. Miller i sitt StructureMap-prosjekt. For det jeg vet kan det hende Ayende også har implementert dette i Rhino Mocks direkte by now.., man vet aldri hva den fyren får til på en ettermiddag.

Stubs

Microsoft Research har sluppet et rammeverk de kaller Stubs, som ble utviklet for deres nye testautomatiserings-verktøy Pex. Dette skiller seg ut fra de andre ved at det bruker kodegenerering. Det er alltid interessant å se hva som kommer ut av MS Research, og selv om jeg ikke vet om dette er interessant som en erstatning for Rhino eller Moq, så er det i alle fall en ny og spennende approach. Sjekk ut Getting Started with Stubs for en lynrask introduksjon.

Merk forøvrig at current versjonsnummer er 0.15.40714.1, så jeg vet ikke om dette er “produksjonsklart” for å si det sånn :)

NMock / Nmock2

Så vidt jeg vet var NMock det første mockingrammeverket på .NET plattformen, laget som en port av DynaMock fra Java. Versjon 2 av NMock ble utviklet av et annet team, og er implementert etter designet fra jMock. Interessen for disse rammeverkene har vært dalende, og det har flere svakheter i forhold til de mer moderne konkurrentene: Den viktigste forskjellen er at mockene ikke er typet ved compile time – man bruker “magiske strenger” for referanser, noe som kan være skummelt og vanskelig å vedlikeholde når ting i designet endres.

Er du interessert i å se nærmere på forskjellene på de fem rammeverkene jeg har nevnt sålangt – moq, TypeMock Isolator, Rhino Mocks, Stubs og NMock2 – så finnes det et prosjekt på Google Code du kan laste ned som heter Mocking Frameworks Compare. Her har du eksempeltester implementert vha. alle rammeverkene, i tillegg til en performance test som sammenligner dem.

Andre rammeverk

EasyMock er et mockingrammeverk fra Java, men det har blitt portet til .NET-plattformen under navnet EasyMock.NET. Det er egentlig alt jeg vet om dette rammeverket, jeg har ikke hørt om noen som bruker det, og det virker som om det ikke har blitt videreutviklet siden 2004, så det er neppe noe å satse på.

NUnit, “alles favorittrammeverk” for enhetstester, har også støtte for mocking – i NUnit-nedlastingen finner du nemlig en lite kjent dll som heter NUnit.Mocks. Jeg har lest at den har begrensede muligheter i forhold til andre rammeverk, men den kan kanskje være aktuell om man ikke skal ta i bruk avhengighetsisolering fullt ut, men har bruk for det i et par tilfeller, og i tillegg allerede bruker NUnit.

Så det var min oversikt over mockingrammeverk i .NET. Min neste blogpost er en praktisk toturial i utenfra-og-inn programmering hvor jeg kommer til å bruke mye mocking.., så følg med om du er interessert i det!

Til slutt: Har du erfaring med noen av disse rammeverkene er det veldig hyggelig om du legger igjen en kommentar med eventuelle mangler i blogposten. Jeg har ikke brukt alle disse vektøyene selv, og har ikke vært en aktiv “mocker” så veldig lenge. Sett meg på plass om du kan!

Knagger: , , , ,

Siste kommentarer

Torbjørn
PS: Takk til Børge Hansen, som delte SCARF-modellen med meg!...
Børge Hansen
Denne likte jeg veldig godt. Du skriver godt og har gode betraktninger  Keep it up – flere trenger å tørre å lære mer om ledelse – du l...
Tormod
Er egentlig ikke overrasket. F# sin fortè er programmererens produktivitet/kvalitet og anledning til parallell kjøring. Men kjøremotoren har ...
Stian
Ville også prøvd med et større problem (x100 eller x1000 f.eks). Når man snakker så små brøkdeler av et sekund som her så kan tiden for en ell...
Torbjørn
Har ikke sjekket - tar en titt i morgen hvis tid :)...
Einar W. Høst
Mhp tco: hva sier ILSpy?...
Torbjørn
Har ikke sett noe på PSeq før, men kjenner til den typen funksjoner fra blant annet Clojure. Og problemet med slike funksjoner i sammenhenger som de...
Håvard
Veldig bra sammenligning! Har du sett på ytelsen av PSeq.* fra powerpakken? Tipper den vil gi performancehit på små mengder, men kan kanskje resul...
Torbjørn
Jeg kom på en demonstrasjon-variant til jeg burde inkludere, nemlig bruk av list comprehension (en type computation expression (også kalt monads)). ...
Einar W. Høst
Interessant, det blir en trade-off mellom eleganse og fart på en måte. Den funksjonelle løsningen med vanlig filter er ren og pen, mens den imperat...
Creative Commons-lisens
Innholdet på denne bloggen er tilgjengelig under Creative Commons Navngivelse-Ikkekommersiell-DelPåSammeVilkår 3.0 Norge lisens.

Programmeringsbloggen
Kjempekjekt.com

© 2006-2013 Torbjørn Marø

Jeg har vært en profesjonell programmerer siden 1999, og dette er min blogg. Målet med bloggen er å stimulere meg selv og alle andre til kontinuerlig eksperimentering og læring.

Jeg forsøker å være allsidig, og programmerer blant annet i C#, Ruby, Erlang og Clojure.

Jeg praktiserer TDD og andre smidige utviklingspraksiser. Jeg er opptatt av kvalitet og ren kode.

Dette og ganske mye mer kan du lese om på denne bloggen!