Jeg kan huske, at jeg for en del år siden hørte Don Box til afslutningssessionen ved TechEd i Barcelona fortælle om, at noget nær den største sikkerhedsrisiko i .NET var, at alle klasser pr. default ikke er “sealed”.  Jeg ved ikke, om det er en kæmpe sikkerhedsrisiko, men manden havde vel fat i noget (som han som regel har).  Nedarvning er ikke svaret på alles problemer, så det burde vel egentlig være sådan, at man aktivt skal åbne for nedarvning af ens klasse i stedet for som nu, hvor man aktivt skal lukke for det.

Men også ganske små detaljer kan give bugs, hvis man ikke tager højde for potentiel nedarvning.

Jeg stødte selv ind i en lille bug forleden, fordi jeg i min naivitet valgte at nedarve fra en klasse, der ikke var designet til nedarvning.  Der opstod heldigvis ikke noget stort brud på sikkerheden i virksomheden af den grund – i stedet fik jeg bare en almindelig null-reference exception.

Klassen, jeg nedarvede fra, indeholdt bl.a. følgende linjer:

   1:  Assembly assem = this.GetType().Assembly;
   2:  var resourceStream = assem.GetManifestResourceStream(this.GetType(), "ResourceName");

Problemet er kaldene til this.GetType().  Hvis klassen er nedarvet, vil GetType() returnere typen for den nedarvede klasse, og hvis de to klasser ikke ligger i samme assembly, vil .NET kigge i det forkerte assembly efter den angivne resource.  Så lektien må være, at man skal tænke sig nøje om for ikke at putte sealed på sin klasse.

CodeMaid er for alle os, der gør en dyd ud af at arrangere sin kode pæn og læsbar. Desværre ved vi jo også af erfaring, at man ikke skal være langt inde i et projekt før der bliver fokuseret mere at skidtet virker og kan leveres til tiden, end at koden kan læses næste gang man har næsen nede i linjerne i en given del af applikationen.

Jeg har længe brugt StyleCop til at forcere ensartet kodestil, men nogle gange er det noget trivielt at flyttet rundt på metoder, slette blanke linjer m.v. for at tilfredsstille StyleCop. Dette er heldigvis én af CodeMaids mange features, og yderligere kan metoder, felter etc. sorteres alfabetisk, så navigationen bliver endnu lettere.

Når man så er i gang med at rydde op i sin kode, så kan man også kaste et blik på den McCabe complexity score, som beregnes. Den siger noget om hvor kompleks en metode er og vil give en god indikation af hvor en refaktorering er nødvendig (eller hvor man skal starte sit code review).

Du finder CodeMaid som Visual Studio Extension her. Selvom den aktuelle version pt. er 0.4.2 så er den stabil og fungerer upåklageligt i VS2010.

Jeg har for lang tid siden skrevet et indlæg på dotninjas om, hvordan man laver en single file generator (også kaldet et custom tool) til Visual Studio.  Desværre ligger det så lang tid tilbage, at jeg ikke kan grave et link frem.  En single file generator benyttes mange steder til at genere kode ud fra indholdet af en anden fil.  For eksempel benyttes det af Entity Framework til at lave klasser ud fra en XML fil som vist på billedet herunder, hvor Custom Tool er sat til “EntityModelCodeGenerator”.

CustomTool

For at lave en Single File Generator skal man implementere nogle bestemte COM interfaces og registrere sin dll på en bestemt måde i registry, og metoden fra min gamle (bortkomne) artikel har fungeret fint indtil fornylig, hvor jeg langt om længe fik mig en Windows 7 64 bit boks på arbejdet.  Jeg har prøvet alt på den maskine både med Wow32To64, og hvad ved jeg.  Men VS 2010 blev ved med fuldstændig at ignorere mit gamle custom tool.

På MSDN er der dokumentation af, hvordan man laver Single File Generators, og der findes også et sample i VS SDK’en, som beskriver, hvordan man kan lave en Single File Generator vha. VS Extensions.  Eksemplet virker fint, men det er ikke særlig godt beskrevet.  Især er det ikke beskrevet, hvordan man sætter projektet op til at blive kompileret helt korrekt, så registry entries bliver genereret korrekt.  Det håber jeg, at dette indlæg kan råde bod på.

Målet er at lave en Single File Generator, der nemt kan distribueres vha. af en .vsix pakke, så den nemt kan installeres som en Visual Studio Extension.  Dette er noget nemmere end min gamle metode, hvor der skulle registreres en dll vha. regasm.exe.

Det er en forudsætning, at man har Visual Studio Service Pack 1 samt Visual Studio SDK Service Pack 1 installeret.  Når SDK’en er installeret, er der adgang til et par VS projekttemplates, som kan bruges til at lave VS Extensions.  Til at lave en Single File Generator, skal man bruge den template, der hedder “VSIX Project”.

vsixproject

Efter projektet er oprettet, skal man først tilføje nogle referencer til en række dll’er for at få fat i de rigtige COM interfaces.  Det drejer sig om følgende:

  • EnvDTE
  • EnvDTE80
  • Microsoft.VisualStudio.Designer.Interfaces
  • Microsoft.VisualStudio.OLE.Interop
  • Microsoft.VisualStudio.Shell.10.0
  • Microsoft.VisualStudio.Immutable.10.0
  • Microsoft.VisualStudio.Interop
  • Microsoft.VisualStudio.Interop.8.0
  • Microsoft.VisualStudio.Interop.9.0
  • Microsoft.VisualStudio.Interop.10.0
  • VSLangProj
  • VSLangProj2
  • VSLangProj80

Det er muligt, at nogle enkelte referencer ikke er nødvendige.  Det har jeg ikke testet.  Det er i øvrigt vigtigt, at alle referencer har “Embed Interop Types” sat til “False”.

EmbedInteropType

Med det ovennævnte sample følger nogle standardimplementationer af to interfaces IVsSingleFileGenerator og IObjectWithSite.  Det nemmeste er at bruge disse implementationer, så vi kopierer BaseCodeGenerator.cs og BaseCodeGeneratorWithSite.cs.  Bemærk, at BaseCodeGenerator indeholder nogle Trace.WriteLine statements, man skal fjerne for at kompilere, hvis man kopierer de to klasser direkte fra eksempelkoden.

Dernæst tilføjes filen CodeGeneratorRegistrationAttribute.cs, som kommer med SDK’en, og som standard ligger under “C:\Program Files (x86)\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Source\CSharp\RegistrationAttributes\”.  Denne klasse indeholder en implementation af en custom attribute, som VS benytter til at generere de nødvendige registry entries i en såkaldt .pkgdef fil.  Mere om det om lidt.

Det er nu tiden til at implementere selve kodegenereringen.  Dette gøres ved at tilføje en klasse, som skal nedarve fra BaseCodeGeneratorWithSite.

    [ComVisible(true)]
    [Guid("A4008ECD-02C6-418F-A0BD-083461479064")]
    [CodeGeneratorRegistration(typeof(DotNinjasCodeGenerator), 
        "DotNinjas sample C# code generator", 
        vsContextGuids.vsContextGuidVCSProject, 
        GeneratesDesignTimeSource = true)]
    [ProvideObject(typeof(DotNinjasCodeGenerator))]
    public class DotNinjasCodeGenerator : BaseCodeGeneratorWithSite
    {
        protected override byte[] GenerateCode(string inputFileContent)
        {
            return Encoding.ASCII.GetBytes("// Some generated code goes here");
        }
    }

Ikke overraskende er det metoden GenerateCode, der kaldes af VS, når man kører sit custom tool.  Parameteren inputFileContent vil indeholde indholdet af den fil, som vores custom tool bliver anvendt på.

Bemærk den imponerende række attributes på klassen.

Klassen skal nødvendigvis være COM visible og dermed også have en Guid attribute (husk også at gøre hele assembly COM visible).  CodeGeneratorRegistration benyttes som nævnt til at generere de nødvendige registry settings automatisk.  Disse registry settings ryger i en .pkgdef fil.  Bemærk vsContextGuids.vsContextGuidVCSProject som angiver, at vores kodegenerator vil generere C# kode.  ProvideObject fortæller VS, hvilken klasse der skal oprettes til at generere koden.  Det er et krav, at klassen markeret med ProvideObject skal have en default constructor.  Bemærk at det lidt overraskende er ProvideObjectAttribute, der bestemmer navnet på vores generator.  Dvs. i dette eksempel er det “DotNinjasCodeGenerator”, der skal benyttes som navnet på vores custom tool i VS.

Projektet er nu klar til at kompilere, og der kommer ganske rigtigt en .vsix fil ud som resultat.  Desværre er det ikke nok.  Der mangler en dll og der mangler en .pkgdef fil med de nødvendige registry entries.  For mig var det lidt et mysterium, hvordan jeg fik disse filer genereret, men det kræver, at man redigerer direkte i .csproj filen.  Så start din favorit csproj editor (såsom notepad) og slet følgende linjer:

    <GeneratePkgDefFile>false</GeneratePkgDefFile>
    <IncludeAssemblyInVSIXContainer>false</IncludeAssemblyInVSIXContainer>
    <IncludeDebugSymbolsInVSIXContainer>false</IncludeDebugSymbolsInVSIXContainer>
    <IncludeDebugSymbolsInLocalVSIXDeployment>false</IncludeDebugSymbolsInLocalVSIXDeployment>
    <CopyBuildOutputToOutputDirectory>false</CopyBuildOutputToOutputDirectory>
    <CopyOutputSymbolsToOutputDirectory>false</CopyOutputSymbolsToOutputDirectory>

GeneratePkgDefFile skal væk, så vi får vores .pkgdef fil, og CopyBuiltOutputToOutputDirectory forhindrer, at der bliver kompileret en dll.  Tilføj desuden følgende PropertyGroup i .csproj filen og gem den:

  <PropertyGroup>
    <RegisterOutputPackage>true</RegisterOutputPackage>
    <RegisterWithCodebase>true</RegisterWithCodebase>
  </PropertyGroup>

Efter reload af den nye projektfil, vil en kompilering give følgende output:

projectoutput

Man kan nu teste pakken ved at trykke F5 for debug, hvilket vil starte en ny instans af VS, hvor man kan afprøve vores custom tool.  Vi vil i stedet prøve vores vores custom tool i den virkelige verden.  Så start med at lukke alle instanser af VS og dobbeltklik på .vsix filen og tryk “Install”.  Dette vil installere vores custom tool som en VS Extension, og ved opspart af VS vil man ganske rigtigt kunne finde DotNinjasSingleFileGenerator som installeret extension.  Ønsker man at angive en lidt mere sigende beskrivelse end bare “Empty VSIX project”, kan det gøre ved at rette i source.extension.vsixmanifest filen i VS.

extension

For at teste DotNinjasSingleFileGenerator, opret et nyt C# projekt, tilføj en fil af en type, som normalt ikke benyttes i et C# projekt (f.eks. en .txt fil men ikke en .cs fil) og angiv DotNinjasCodeGenerator som custom tool, da det var DotNinjasCodeGenerator, der blev angivet som type til ProviderObjectAttribute.

customtoolinaction

Højreklik på din fil og vælg “Run custom tool”, hvilket vil resultere i en .cs fil indholdende den genererede kode.

På en tidligere version af dotninjas.dk kunne man finde en artikel om hvordan man kompilerer et udtryk til funktioner som kan bruges som plugin i egne beregninger - ved at benytte den indbyggede C# compiler. Jeg vil forsøge at genopfriske lidt for artiklen, men vil denne gang compile Lambda udtryk som f.eks.

   1:  x => x * 2;

Vores specialiserede interface erstattes nu af Expression<T> (i dette tilfælde Expression<Func<double, double>>). Dette er naturligvis intressant hvis man ønsker at arbejde videre med expression træet, f.eks. hvis man ønsker at differentiere udtrykket. Hvis man blot ønsker at beregne funktionsværdier anbefaler jeg at man returnerer Func<double, double> delegaten direkte, idet der skal kaldes Compile på et lambda udtryk for at få delegaten vi skal evaluere med - man kompiler altså i to omgange.

Grundlæggende er det dette stykke kode vi ønsker at compile runtime:

   1:  using System; 
   2:  using System.Linq.Expressions; 
   3:   
   4:  public static class LambdaParser 
   5:  { 
   6:    public static Expression<Func<double,double>> Parse() 
   7:    { 
   8:      return {expression}; 
   9:    } 
  10:  } 

Vi skal blot erstatte {expression} med det ønskede udtryk som skal være en funktion med et enkelt argument af type double og return værdi double, eller f:R->R som man siger på matematisk. Man kunne også lægge sig fast på at variablen hed x og erstatte {expression}; med x=>{expression};

Jeg har valgt C# men man kan også vælge VB eller et andet sprog hvis blot man har en passende CodeDomProvider.

   1:  CSharpCodeProvider provider = new CSharpCodeProvider(); 

Brug evt. en Dictionary med CompilerVersion="v3.5" hvis du har behov for en specific version, ellers brug config filen. Ellers skal vi blot definere vores referencer og kompile koden:

   1:  CompilerParameters cp = new CompilerParameters(); 
   2:  cp.GenerateInMemory = true; 
   3:  cp.ReferencedAssemblies.Add("System.Core.dll"); 
   4:   
   5:  CompilerResults cr = provider.CompileAssemblyFromSource(cp, code); 

Husk at checke for kompilefejl (se cr.Errors.HasErros), for der bliver ikke kastet en exception. Herfra er der 3 linjer kode til vi står med vores lambda udtryk:

   1:  Type parserType = cr.CompiledAssembly.GetType("LambdaParser"); 
   2:  MethodInfo mi = parserType.GetMethod("Parse"); 
   3:  var f = (Expression<Func<double,double>>)mi.Invoke(null, new object[] { })); 

Jeg lader det være op til læseren at indkapsle koden i en genbrugelig klasse og tilføje bells and whistles. Ekstra referencer vil være en oplagt mulighed for udvidelser. En anden oplagt mulighed er at indføre en potens funktion hvad man skal bruge matematiske formler. Det kræver dog at man parser udtrykket lidt mere end blot en søg og erstat, f.eks.

   1:  x+x^2 -> x + Math.Pow(x, 2) 

Her skal Math.Pow funktionen indsættes efter plus tegnet, mens den skal indsættes efter parentens i dette udtryk

   1:  (x+1)^2 -> Math.Pow(x+1,2) 

Heldigvis skal de øvrige operatorer behandles ens, så det kun er parenteser vis skal ta' højde for.

   1:  public static string ReplacePower(string s) 
   2:  { 
   3:    int powerIndex = s.IndexOf('^'); 
   4:    if (powerIndex > 0) 
   5:    { 
   6:      string right = s.Substring(powerIndex + 1); 
   7:      string left = s.Substring(0, powerIndex); 
   8:   
   9:      int leftStart; 
  10:      int rightEnd; 
  11:   
  12:      if (left[left.Length - 1] == ')') 
  13:      { 
  14:        leftStart = FindStartParenthesis(left) - 1; 
  15:      } 
  16:      else 
  17:      { 
  18:        leftStart = left.LastIndexOfAny(opChars); 
  19:      } 
  20:      if (right[0] == '(') 
  21:      { 
  22:        rightEnd = FindEndParenthesis(right); 
  23:      } 
  24:      else 
  25:      { 
  26:        rightEnd = right.LastIndexOfAny(opChars); 
  27:      } 
  28:   
  29:      string s0 = left.Substring(0, leftStart + 1); 
  30:      string s1 = left.Substring(leftStart + 1); 
  31:      string s2 = rightEnd >= 0 ? right.Substring(0, rightEnd) : right; 
  32:      string s3 = rightEnd >= 0 ? right.Substring(rightEnd) : string.Empty; 
  33:   
  34:      return ReplacePower(string.Format("{0}Math.Pow({1},{2}){3}", s0, s1, s2, s3)); 
  35:    } 
  36:    return s; 
  37:  } 
  38:   
  39:  private static char[] opChars = new char[] { '+', '-', '/', '*', ' ', ',' }; 
  40:   
  41:  private static int FindStartParenthesis(string s) 
  42:  { 
  43:    int index = s.Length - 1; 
  44:    int parCount = 0; 
  45:   
  46:    while (index >= 0) 
  47:    { 
  48:      if (s[index] == ')') parCount++; 
  49:      if (s[index] == '(') parCount--; 
  50:      if (parCount == 0) return index; 
  51:      index--; 
  52:    } 
  53:    return index; 
  54:  } 
  55:   
  56:  private static int FindEndParenthesis(string s) 
  57:  { 
  58:    int index = 0; 
  59:    int parCount = 0; 
  60:   
  61:    while (index < s.Length) 
  62:    { 
  63:      if (s[index] == ')') parCount--; 
  64:      if (s[index] == '(') parCount++; 
  65:      if (parCount == 0) return index; 
  66:      index++; 
  67:    } 
  68:    return index; 
  69:  } 

Med FindEndParenthesis metoden skulle det også være nemt at erstatte diverse matematisk funktioner
med deres System.Math ekvivalenter som cos -> Math.Cos og sin -> Math.Sin.

Det var lide kode til at komme igang med din egen runtime expression parser - uden at der er nogen garanti for at det virker. Husk i det mindste at indsætte diverse checks så det ikke brager ned midt i en vigtig beregning.

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)