Det er fredag, så her er et par party tricks til at vise frem over fredagsøllen :-) De viste tricks er tyvstjålet fra en kollegas præsentation på et internt gå-hjem-møde..

Testdata indenfor en given kategori kan hurtigt genereres således (til ære for ne0san vælger vi banker..)

 

Hold CTRL nede og træk i nedre højre hjørne, og vupti..

 

Skal man have lidt hjælp til oversættelsen kan dette også klares direkte fra regnearket;

 

Jeg er egentlig fint tilfreds med de farver Visual Studio 2008/2010 anvender til syntax farvning, men hvis man er til andre farver kan man jo starte med andres farveschemaer. ScottGu twittede et tip om studiostyles.info, hvor man kan dele schemaer.

Et par af schemaerne mindede mig om mine første programmeringssessioner i folkeskolens edb-lokale, fx det monokrome PDP-11

Eller de klassiske Commodore 64 farver;

Jeg har lidt overvejet en af schemaerne med sort baggrund - de ser jo rigtig kodeagtig cool ud, men bliver man ikke træt i øjnene af at se på en sort skærm?

Allerførst et ønske om en glædelig jul og et godt nytår :-)

 Dernæst et lille tip om at du kan erhverve dig en licens til Linqpad for kun 100kr ($19) frem til 31. december - den kan installeres på op til 3 maskiner samtidigt og er en varig licens til fremtidige versioner.

Hvis du ikke allerede kender Linqpad så er det et glimrende tidspunkt at teste det. Personligt bruger jeg den til mange af de situationer, hvor man liiige laver et lille testprojekt for at teste en stump kode - og ikke mindst til linq queries, entity framework forespørgsler, regex patterns etc.

Tilmeld dig her

 Ja, yderligere introduktion af Scott Guthrie behøves vel ikke, men har du sovet under en sten de sidste mange år, så start med at se dokumentaren om Visual Studio på channel 9. Dokumentaren kan i øvrigt også anbefales selvom du kender både ScottGu og Visual Studio :-)

 Ses vi?

Måske er jeg ved at blive gammel, eller måske er jeg blot kommet til den indsigt, at bliver man længe nok på samme arbejdsplads, så er så godt som alle nye og spændende projekter også ensbetydende med endnu et projekt, der skal vedligeholdes over tid. Selv projekter, der blot skal skydes af en enkelt gang, har en tendens til med jævne mellemrum at dukke frem til en ny omgang. Hvad enten det skyldes det ene eller det andet, så er ord som standardisering, genkendelse, testbar begyndt at fylde mere og mere når nye projekter angribes - der er jo stor sandsynlighed for at jeg selv ender med aben, når min egen kode på sigt skal vedligeholdes :-)

 Jeg har noget tid anvendt StyleCop til at forcere en ensartethed ned over min kode. Ja, det kan være mega-irriterende at være tvunget til at skrive kommentarer og flytte rundt på metoder og properties for at tilfredsstille StyleCop, men i sidste ende bliver koden lettere at læse, når man gør det på samme måde på tværs af projekter. For at komme lidt videre i samme spor købte jeg Robert C. Martins Clean Code: A Handbook of Agile Software Craftsmanship

Mine forventninger til bogen var lidt de samme som da jeg i sin tid tog StyleCop i brug, nemlig at det kan godt være at man ved hvordan det skal gøres, men nogle gange kan det være en fordel at blive mindet om det endnu engang. De forventninger lever bogen til fulde op til, men meget mere synes jeg heller ikke der er at hente, hvis man programmeret i nogle år. Bogen anvender eksempler i Java, men kan sagtens læses med tanke på C# eller VB.Net.

En af de ting, jeg har taget til mig fra bogen finder man allerede i første kapitel. Martins kalder den "The Boy Scout Rule" og går i sin enkelhed ud på, at man skal efterlade sin kode pænere end da man fandt den. Med andre ord; hver gang man er i et hjørne af sin kode, man ikke har været i længe, så brug lige et øjeblik på at gøre koden lidt pænere. På den måde bliver hele projektets kode gradvis bedre med tiden.

Andre udsagn finder jeg knapt så logiske. Eksempelvis argumenterer han under navnekonventioner for, at et interface ikke skal prefixes med stort I. Det distraherer er argumentet, men her vil jeg nok fortsætte med at følge StyleCops regler, der netop påtvinger I som prefix. De fleste regler er dog gode at få genopfrisket, og meget praktisk er disse regler samlet i kapitel 17 - Smells and Heuristics. Langt de fleste, der har programmeret nogle år vil formodentlig kunne nøjes med de 30 sider som dette kapitel udgør, og disse 30 sider kan med fordel skimmes med jævne mellemrum. 

Det virker som om forfatteren har haft lidt svært ved at beslutte sig for hvem bogen skal skrives til. På den ene side genopfriskes helt basal viden som at navnet "modificationTimestamp" er bedre end "modymdhms" (selvom ymdhms afledt af formatet - yy/mm-dd hh:mm:ss). På den anden side forventes en grad af indsigt, når der pludselig introduceres en stump unit test kode ca. 20 sider før kapitlet om unit tests. Ca. 150 sider af bogens 400 sider er to marathon eksempler, hvor bogens regler og refaktoreringsmetoder hældes ned over to rigtige cases. Sider, der i bedste fald blot ender med at blive skimmet - selv hvis man uøvet ud i at vedligeholde kode.

Alt i alt en udmærket, men lidt kedelig bog, til at minde én om at gøre al kode lidt bedre, og ikke blot opfylde kravsspec på kort sigt. Hvis jeg var chef ville jeg tvinge mine medarbejdere til at læse den - jeg tror mange, ville have gavn af en opfrisker. Vi ender på 4 små ninjastjerner pga. value-for-money. Set til kr. 243,-

Det er endelig lykkedes mig at finde et lille projekt, som jeg kan køre strengt efter TDD metodikken, med andre ord testen skrives først. I den forbindelse er der noget mere fokus på access modifiers end når vægten er på integrationstests, men man må jo hænge i og gøre plads til injektere fake objekter efter bogen.

Mens jeg er godt i gang med implementering af validering i et business objekt støder jeg så ind i en "hvorfor-nu-det" situation, som stadig ikke er helt klar for mig. Vi har følgende setup eksempliceret ved det klassiske books eksempel.

I klassen "BookBusinessObject" ønsker vi at teste om en given bog (CurrentBook) findes i en liste af bøger (ListOfBooks). Listen kan fx initialiseres via opslag i en database men for at lette presset lidt på databasen gemmes listen i en statisk liste (books). Den unge dansker skriver sin unit test og får en fin grøn lampe fra NUnit med følgende kode;

    1 using System.Collections.Generic;

    2 using NUnit.Framework;

    3 using Rhino.Mocks;

    4 

    5 public class Book

    6 {

    7     public string Author { get; set; }

    8     public string Title { get; set; }

    9 }

   10 

   11 public class BookBusinessObject

   12 {       

   13     private static List<Book> books;

   14 

   15     public Book CurrentBook { get; set; }

   16 

   17     public virtual List<Book> ListOfBooks()

   18     {

   19         if (books == null)

   20         {

   21             InitializeBooks();

   22         }

   23 

   24         return books;

   25     }

   26 

   27     private static void InitializeBooks()

   28     {

   29         List<Book> bookList = new List<Book>();

   30         bookList.Add(new Book { Title = "Microsoft .NET: The Programming Bible", Author = "O'Brien, Tim" });

   31         bookList.Add(new Book { Title = "XML Developer's Guide", Author = "Gambardella, Matthew" });

   32 

   33         books = bookList;

   34     }

   35 

   36     public bool IsBookInList()

   37     {

   38         foreach (Book b in ListOfBooks())

   39         {

   40             System.Console.WriteLine(b.Title);

   41         }

   42 

   43         return this.ListOfBooks().Contains(this.CurrentBook);

   44     }

   45 }

   46 

   47 [TestFixture]

   48 public class BookBusinessObjectTest

   49 {

   50     [Test]

   51     public void IsBookInList_BookInList_ReturnTrue()

   52     {

   53         // arrange

   54         List<Book> fakeBookList = new List<Book>();

   55         Book fakebook = new Book { Title = "TDD for dummies", Author = "John Doe" };

   56         fakeBookList.Add(fakebook);

   57 

   58         BookBusinessObject businessObjectMock = MockRepository.GenerateStub<BookBusinessObject>();

   59         businessObjectMock.Stub(x => x.ListOfBooks()).Return(fakeBookList);

   60 

   61         businessObjectMock.CurrentBook = fakebook;

   62 

   63         // act

   64         bool included = businessObjectMock.IsBookInList();

   65 

   66         // assert

   67         Assert.IsTrue(included);

   68     }

   69 }

Der hviles et par minutter på laurbærerne inden næste test skrives. Det er ikke nok at vide om en given bog findes i listen, der skal også tilføjes andre valideringsregler så der kan med fordel oprettes en IsValid() metode, der kalder alle business objektets valideringsregler og giver en samlet vurdering af om business objektet er valid.

I unit testen for IsValid() kan det være en fordel at vi i stedet for at lave en fakeBookList blot laver et fake metodekald til IsBookInList() der altid returnerer true. Dermed skal der ikke sættes en masse op for at test IsValid(), hvis vi antager at IsBookInList() blot er én af mange ting, der skal være opfyldt.

Så for at Rhino Mocks kan fake retursvaret fra IsBookInList() ændrer vi accessoren i IsBookInList() fra "public" til "public virtual", men hov!! Det giver problemer, for nu siger NUnit;

TestCase 'BookBusinessObjectTest.IsBookInList_BookInList_ReturnTrue' failed:
  Expected: True
  But was:  False

0 passed, 1 failed, 0 skipped, took 2,64 seconds (NUnit 2.4).

Og det store spørgsmål er; hvorfor nu det?!?

Hjælpelinjerne 38-41 angiver, at fakeBookList ikke bliver injekteret da ListOfBooks() er tom. Ændres accessoren i stedet til "internal virtual" så er testen igen OK. Hvor er det at filmen knækker? Og hvad er best practice?

Nogle gange kan man være heldig at finde lidt skjult guld gemt i andre blogindlæg.

 I et indlæg om XML i VB.Net forklarer Jim O’Neil hvordan man kan installere et lille addin til Visual Studio, der gør det muligt at indsætte XML fra udklipsholderen til VS som C# kode. Addin'et findes som eksemplet "LinqSamples/PasteXmlAsLinq" i C:\Programmer\Microsoft Visual Studio 9.0\Samples\1033\CSharpSamples.zip, og skal blot kompileres og flyttes til mappen C:\Documents and Settings\[brugernavn]\Dokumenter\Visual Studio 2008\Addins

Dermed bliver et fragment fra det klassiske books.xml eksempel:

    1    <book id="bk110">

    2       <author>O'Brien, Tim</author>

    3       <title>Microsoft .NET: The Programming Bible</title>

    4       <genre>Computer</genre>

    5       <price>36.95</price>

    6       <publish_date>2000-12-09</publish_date>

    7       <description>Microsoft's .NET initiative is explored in

    8       detail in this deep programmer's reference.</description>

    9    </book>

Konverteret til:

    1 XElement xml = new XElement("book",

    2     new XAttribute("id", "bk110"),

    3     new XElement("author", "O'Brien, Tim"),

    4     new XElement("title", "Microsoft .NET: The Programming Bible"),

    5     new XElement("genre", "Computer"),

    6     new XElement("price", "36.95"),

    7     new XElement("publish_date", "2000-12-09"),

    8     new XElement("description",

    9         "Microsoft's .NET initiative is explored in \n" +

   10         "      detail in this deep programmer's reference."

   11     )

   12 );

 

Når det indsættes via menuen Edit -> "Paste XML as XElement", der er synlig, når udklipsholderen indeholder XML. Praktisk Smile

Kan hentes her:

 (Via http://geekswithblogs.net/Shadowin/archive/2009/04/13/illustrated-c-2008-ebook.aspx)

ne0san og jeg fulgte for nogle år siden kursuset "Design af brugergrænseflader og data" på ITU, og én af de ting, jeg tog til mig fra det kursus var mantraet om at inddrage brugerne så tidligt i designfasen som muligt. Man bliver hurtigt forelsket i en feature eller en layout finesse, som man har brugt (for) lang tid på.

Når man har en problemløser tilgang til tingene kan det dog være lidt kedeligt at sætte sig ned og tegne brugergrænseflader - i stedet for at komme i gang med at skrive kode. Og så ender man hurtigt i det spor, hvor brugerne bliver inddraget relativt sent i processen. I går aftes faldt jeg over Balsamiq til at lave mockups, og det virker ret intuitivt at gå til. Ud over at det er lige til at gå til med en masse standard GUI elementer, så bliver elementerne vist som stregtegninger. Dermed får man signaleret til brugerne, at dette kun er et designoplæg (en HTML mockup kommer hurtigt til at se næsten færdig ud) og samtidigt skal man ikke hele tiden "tegne om", som hvis det var en blyant mockup.

Man kan prøve Balsamiq gratis på deres hjemmeside og nedenstående er hvad jeg nåede at lave af designet fra irun.dk på de 5 minutter man har til rådighed før en lille nag-dialogboks bliver vist (man kan fortsætte efter det). Det syner måske ikke af meget, men det er inklusiv tiden til at sætte sig ind i at bruge applikationen Smile

Applikationen er noget Flash-halløj, men man kan gemme (og hente) designet som XML, så i princippet er der jo mange muligheder for at arbejde videre direkte fra den første mockup.

 
(Og så fik jeg også prøve at lave screenshots og blogindlæg fra min Mac Wink ) 

ne0san har prikket lidt til mig på det seneste med slet skjulte hentydninger til at få opdateret med et nyt indlæg, og da jeg tidligere på ugen skulle lave en simuleret test af belastningen på vores servere ved mange samtidige brugere, fik jeg en anledning til at komme lidt i gang med et indlæg, som jeg et stykke tid har tænkt på at få skrevet.

I modsætning til ne0san og t4rzan bliver det meste af den kode, jeg skriver, anvendt i asp.net og meget test er derfor test af GUI, forskellige browsere etc. Der er mange gode ting ved unit-testing, men jeg har stadig et web-projekt til gode, hvor unit-testing udgør en væsentlig del af testningen. I stedet går der rigtig meget tid med at klikke, ændre, klikke, forholde sig til feedback på skærmen osv. I nogle formularer kan vi have op mod 200 inputfelter på en side, der udløser validering, beregner delsummer etc., og her kan det være optimalt med en automatiserede test til at udfylde felter og klikke på knapper, så man blot skal forholde sig til, om fx en javascript funktion regner rigtigt.

Det er lige præcis hvad WatiN kan! WatiN er et .Net framework til at automatisere tests i Internet Explorer og Firefox (samt snart Chrome). Det er enormt let at komme i gang med - en reference til WatiN.Core.dll, en "using WatiN.Core;" og så er man kørende. Her fra er det "blot" at kode de linjer, der skal til for at udfylde felter, klikke på knapper, etc. Desværre er det ikke altid, at man bare kan referere til ID på en kontrol. Det findes måske ikke, eller kontrollen bliver genereret runtime eller lign., men heldigvis stiller WatiN en masse FindBy-metoder til rådighed, når kontrollerne skal findes. I eksemplet nedenfor refereres fx direkte til ID, der anvendes "alt" attributten, RegEx m.m. - jeg har med vilje gjort det mere omstændigt end nødvendigt for at komme lidt mere rundt i frameworket.

Indtil videre har jeg klaret mig med IE Developer Toolbar og Web Developer Extension til Firefox til at finde gode referencer til kontrollerne, men der findes faktisk et WatiN Test Recorder projekt, der gør det endnu lettere at komme i gang. Jeg har ikke selv prøvet det, men interessant ser det ud - måske kan det på en smart måde kombineres med IronPython som ne0san har leget med på det sidste?

Eksemplet nederst på siden gør følgende;

  1. Åbner et nyt Internet Explorer vindue
  2. Navigerer til http://www.asp.net/ajax
  3. Udfylder ValidatorCallout eksemplet på siden
  4. Skriver resultatet i et konsolvindue.

Når WatiN udfylder felter og klikker på kontroller bliver disse highlighted, så man kan se hvor den er i gang. Tekstfelter bliver udfyldt ét tegn af gangen, lige som når man selv sidder og taster, og derved er det let at følge med i hvorvidt valideringsregler etc. udløses korrekt.

Her følger så selve koden, der i sig selv viser et meningsløst eksempel, men bl.a. illustrerer hvor uhyggeligt let det er at lave en dims, der kan spamme en formular Wink

    1 using System;

    2 using System.Collections.Generic;

    3 using System.Text;

    4 using System.Text.RegularExpressions;

    5 using WatiN.Core;

    6 

    7 namespace WatinExample

    8 {

    9     class Program

   10     {

   11         [STAThread]

   12         static void Main(string[] args)

   13         {

   14             // Åben en ny Internet Explorer

   15             IE ie = new IE();

   16 

   17             // Maksimer browservinduet, så vi kan se hvad vi laver..

   18             ie.ShowWindow(NativeMethods.WindowShowStyle.ShowMaximized);

   19 

   20             // Gå til AJAX siden på www.asp.net

   21             ie.GoTo("http://www.asp.net/ajax");

   22 

   23             // Klik på billedet "View the Control Toolkit"

   24             // Billedet har ingen ID, så vi finder det ved

   25             // at søge efter den alternative tekst for billedet,

   26             // som heldigvis findes

   27             ie.Image(Find.ByAlt("View the Control Toolkit")).Click();

   28 

   29             // Klik på billedet "View the Control Toolkit, Live"

   30             // Billedet har ingen ID, så denne gang finder vi

   31             // det via en RegEx, der søger efter filnavnet,

   32             // (go-toolkit.gif), der er angivet i Src-tag:

   33             // http://static.asp.net/asp.net/images/ajax/go-toolkit.gif

   34             Regex regex = new Regex("(go-toolkit.gif)");

   35             ie.Image(Find.BySrc(regex)).Click();

   36 

   37             // Klik på linket med ID="ctl00_SamplesLinks_ctl34_SamplesLink"

   38             // (ValidatorCallout eksemplet)

   39             ie.Link("ctl00_SamplesLinks_ctl34_SamplesLink").Click();

   40 

   41             // ValidatorCallout eksemplet tager to input-felter (navn og telefonnr)

   42             // og returner med et svar fra en webservice, når der klikkes på en

   43             // knap. Vi udfylder de to felter og klikker på knappen "Submit".

   44             ie.TextField("ctl00_SampleContent_NameTextBox").TypeText("pandasan");

   45 

   46             Random rnd = new Random();

   47             string phoneNumber = string.Format("({0}) {1}-{2}", rnd.Next(100, 999), rnd.Next(100, 999), rnd.Next(1000, 9999));

   48             ie.TextField("ctl00_SampleContent_PhoneNumberTextBox").TypeText(phoneNumber);

   49             ie.Button(Find.ByValue("Submit")).Click();

   50 

   51             // Venter på svar fra webservice, så vi sover lidt imens..

   52             System.Threading.Thread.Sleep(5000);

   53 

   54             // Skriv svaret i konsollen

   55             Console.WriteLine(ie.Span("ctl00_SampleContent_lblMessage").Text);

   56 

   57             // Smil til kameraet..

   58             ie.CaptureWebPageToFile(@"c:\temp\screenshot.jpg");           

   59 

   60             // Luk Internet Explorer

   61             ie.Close();

   62 

   63             Console.ReadKey();

   64         }

   65     }

   66 }