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 }

I en tidligere post så vi hvor nemt det var at komme igang med IronPython og jeg håber det kom frem at det virkligt er mærkbart nemmere end at skulle til at bruge C#/VB.Net som scriptsprog. Hvis ikke så prøv at forklar dig selv hvordan et enkelt delegate kald (som f.eks. add(2, 3)) skal wrappes for at køre som C# script. Eller hvor meget reflection du skal bruge for at få adgang til en delegate eller kallse du har definere i dit script.

Nu vil jeg forsøge at beskrive det sidste i IronPython, altså hvordan man kan bruge Python klasser i C#. Vi begynder med at definere en simpel Python klasse.

class MyClass(object):
    def add(self, a, b): return a + b

Når koden er kørt vil vores scope indeholde en variabel med navnet på klassen, her MyClass.

object myClass = scope.GetVariable("MyClass"); 

Dette er klassedefinition som vi skal bruge til at oprette en ny instans of klassen og til at kalde methoder på klassen. Vi skal også bruge ObjectOperations fra vores engine.

ObjectOperations op = engine.Operations; 

Herefter er det bare at gå igang med at oprette en instans, finde methoden og kalden metoden:

object instance = op.Call(myClass);
object method = op.GetMember(instance, "add");
int sum = (int)op.Call(method, 2, 3);

Hvis sum ikke er 5, så gik det galt (and you're on your own). Det var det. Måske nogle kan genkende et mønster fra IDispatch.Invoke, eller der det bare mig der fik et deja-vu. Med dynamic i C# 4.0 skulle det blive meget nemmere med disse operationer, fordi det er speciel designet til at kode op mod f.eks. Office objekt modellen, men det ser vi på til den tid.

Jeg var egentlig i gang med at skrive et indlæg om hvor let det var at integrere IronPython i egne programmer, da jeg stødte på A 3 minute guide to embedding IronPython in a C# application. Så indlægget blev sat på stand-by, men det er et sjovt emnu og nu kommer det så alligevel - men vi prøver at gøre det på kun 2 minutter.

Lige en hurtig disclaimer inden kommer for godt i gang: Jeg er ny til Python som sprog og platform. Det med de to minutter er måske også en underdrivelse, men den tid det ta'r at komme i gang med IronPython er stærkt korreleret med hastigheden for på din internetforbindelse.

Det først der skal ske er at du downloader IronPython fra http://www.codeplex.com/IronPython. Minimum requirement for IronPython 2.0 er .NET 2.0 SP1. Det vil kræve den største del af de to minutter. Du kan bruge Python konsollen (ipy.exe) til at eksperimentere med Python og så er du sådan set i gang. Den medfølgende tutorial kan anbefales hvis du vide mere om integrationen mellem .NET og Python.

Det sjove kommer når man begnder at integrere IronPython med egne programmer. IronPython er baseret på Dynamic Language Runtime (DLR) og bortset fra det rent Python specifikke, så vil andre DLR sprog kunne integreres på samme måde. Så selvom Python konsollen er smart vil du nok begynde at kede dig efter to minutter.

Det eneste du skal bruge er en ScriptEngine og et ScriptScope.

ScriptEngine engine = Python.CreateEngine();
ScriptScope scope = engine.CreateScope();

Tada. Du skal naturligvis referere IronPython.dll, Microsoft.Scripting og Microsoft.Scripting.Core. ScriptEngine er det springende punkt for valgt af sprog. Andre sprog, som f.eks. IronRuby, sættes op på lignende måde, men med en anden engine.

Så er det bare at fyre noget kode afsted. Lad os starte med et simpelt udtryk:

string input = "3+3";
ScriptSource source = engine.CreateScriptSourceFromString(input, SourceCodeKind.Expression);
source.Execute(scope);

Det er i sig selv ikke så imponerende, de fleste af os kunne regne det ud i hovedet. Men hvis vi skal køre egentlig Python kode skal SourceCodeKind blot ændres til f.eks. Statements eller File.

Vi er endnu ikke kommet meget længere end vi var med ipy.exe, så lad mig lige bruge det sidste af de 2 minutter på at vise hvor nemt det er at integrere dine C# klasser. For at gøre variable tilfængelige i IronPython skal de blot tilføjes til dit scope.

scope.SetVariable("xyz", new Xyz());
scope.SetVariable("add", new Func<double, double, double>(delegate(double a, double b) { return a + b; }));

Den sidste variable bliver en funktion og svarer til det klassiske begynder-eksempel:

def add(a, b):
    return a + b;

Xyz klassen fungerer nu som enhver anden klasse, som beskrevet i Tutorial. Prøv evt med dir(xyz).

Håber det giver dig inspiration til at prøve hvordan du kan integrere IronPython med dine egne programmer.

Jeg har fornylig lavet min første rigtige ASP.NET MVC løsning.  Det er faktisk super cool at arbejde med.  Det eneste, jeg ikke er så vil med, er behovet for at lave sine views à la klassisk ASP, hvor man bruger <% %> tags.  Til fordel fra klassisk ASP snakker vi dog nu typestærkt C#.  Desværre bliver alle views først kompileret runtime, så eventuelle fejl i views opdages først under debugging.

En virkelig lækker ting ved ASP.NET MVC er, at det er utroligt nemt at få til at spille sammen med Dependency Injection frameworks, som f.eks. Unity.  Med Unity kan man f.eks. uden det store besvær lave constructor DI på sine controller klasser.  Der findes masser af eksempler på det på nettet.

Derudover har jeg gjort mig et par erfaringer, som jeg hermed giver videre:

  • Benyt altid IIS'en til at udvikle på og ikke ASP.NET development server, som VS.NET starter op under debug (det gælder vel altid og ikke kun for MVC).
  • Sørg for at sætte routing og IIS'en op til at benytte .mvc extension fra start af (gælder kun IIS 6) - eller brug url-rewriting.  Jeg har ikke noget problem med at bruge .mvc extensions, for pæne urls er ikke mit formål med MVC, men nogle finder det grimt, og så er url-rewriting tilsyneladende en løsning.
  • Benyt hjælpefunktionerne på Html klassen så meget som muligt.  Det gælder især Html.ActionLink, Html.RouteLine og Html.BeginForm.  På den måde sikrer man, at alle links altid er rigtige ift. websitets root folder.

En af de løsningsmetoder som jeg skitserede under mit indlæg Thread-safe Random Numbers var at oprette en tråd som stod for genereringen af de tilfældige tal. Den metode skulle sikre at resultat var reproducerbart og at man stadig kunne bruge sin favorit talgenerator (Mersenne Twister er et godt bud på en favoritgenerator). Selvom det er en relativt simpel løsning, betyder det jo ikke at man ikke skal tænke sig om. Min første indskydelse var at generere en masse uniform variable i en pool og så transformere dem til f.eks. normalfordelte i løbet af selve simuleringen. Det minimerer tidsforbruget i generatortråden og overlader det til simuleringstrådene.
Det giver dog et lille problem.

Den bedste måde at generere normalfordelte tilfældige tal er at benytte polar Box-Müller som er givet ved

do 
{ 
  x = 2.0 * Random() - 1.0; 
  y = 2.0 * Random() - 1.0; 
  w = x * x + y * y; 
} 
while (w >= 1.0 || w == 0.0) 
w = Math.Sqrt(-2.0 * Math.Log(w) / w); 
x *= w; 
y *= w; 

Ovenstående kode giver os to normaltfordelte tal, men bruger MINDST to ligefordelte tal. Vi ved altså ikke på forhånd hvor mange ligefordelte tal vi skal bruge, hvilket forkaster min udmiddelbare indskydelse. I stedet vil jeg nu skabe en række streams som er generede med den ønskede fordeling. Generator tråden får altså mere arbejde, men alternativet er ikke et reelt alternativ, da det er svært at vide om man er på den sikre side hvis man vil generere rigeligt med ligefordelt tal.

Man kunne indvende at jeg kunne vælge en anden form af Box-Müller, som bruger præcis to ligefordelte tal til at generere to normal fordelte. Hvis vi ser bort fra at denne metode er langsommere og mindre numerisk stabil, så er der jo andre fordelinger som har samme problem, f.eks. Poissonfordelingen. Det antyder at vi bare skal løse problemet med det samme.

Følgende stump kode er den anbefalede måde at bruge monitors på, og det er identisk med den kode, som C#'s lock statement genererer.

Monitor.Enter(lockobject);
try
{
    //...
}
finally
{
    Monitor.Exit(lockobject);
}

 

Der er faktisk en potentiel bug i denne kode (iflg. Joe Duffy).  Kompileren er i sin fulde ret til at indsætte instruktioner mellem Monitor.Enter og try.  Et forsøg viser, at C# kompileren ganske rigtigt indsætter en NOP instruktion i debug build. 

Det betyder, at man (hvis man er meget uheldig) kan opleve asynkrone thread exceptions for netop denne instruktion.  Det kan f.eks. ske, hvis en anden tråd kalder Thread.Abort.  Sker det, vil finally blokken aldrig eksekvere, og vi har en såkaldt abandoned monitor.  Det er nok de færreste, for hvilke det er et reelt problem, men det kan ske.

Det viser sig, at C# teamet har taget højde for dette.  I release builds vil C# kompileren ikke indsætte ekstra instruktioner efter Monitor.Enter, og Monitor.Enter er lavet på en speciel måde, så næste instruktion i vores kode ovenfor vil foregå inde i try blokken.

Joe Duffys bog fik mig til at tænke på connections.  Hvis DbConnection.Open står uden for try blokken, kan man i sjældne tilfælde risikere, at finally blokken indeholdende DbConnection.Close ikke bliver kaldt pga. en exception.  Heldigvis er det et krav til DbConnection.Close, at man skal kunne kalde den flere gange uden at få en exception i hovedet uanset om ens connection er åben eller ej.  Derfor kan man med sindsro flytte DbConnection.Open indenfor try blokken.

En gammel ven og Nikon D300 ejer gjorde mig opmærksom på, at der er kommet et nyt website med tips og tricks videoer for Nikon DSLR brugere: http://www.dtowntv.com/

Desværre fokuserer de kun på de nyere kameraer og dermed ikke D80, men derfor er det meget sjovt at se.