Hvis du har en masse test data i en Excel fil er der ingen grund til at kopiere det ind i dine tests - xUnit extensions kan ordne det for dig. Det er endda på en mege elegant måde.

Jeg har noget test data fra et gammel system som indeholder nogle tal for forskellige aldre. F.eks.

Age, Gamma, Sigma, Lambda, S

Jeg har 130 aldre i skridt på et kvart år. Hvert af de øvrige koloner svarer til det ønskede resultat af en funktion med samme navn givet alderen. Der findes altså funktioner Gamma(age), Sigma(age), Lambda(age) og S(age) på min klasse. Jeg vil gerne teste disse funktioner i hver deres test metode - ikke noget med at teste alle mulige funtioner i samme test metode (fy fy).

Det første du skal gøre er at finde dit test data frem i Excel og navngive området - inkl. kolonenavne. Enten ved at du bruger navneboksen til venstre for formellinjen eller "Name Manager" under "Formulas" (Du skal bruge "Name Manager" hvis du vil ændre eller slette navne). Dernæst gemmer du dit test data i Excel 97-2003 format (*.xls). Jeg har ikke fået det til at virke med det nye fancy pancy format.

Nu skriver du dine test metoder og klistrer de magiske attributer på:

[Theory]
[ExcelData("MyData.xls", "SELECT Age, Gamma FROM TestArea51")]
public void Gamma_Test(double age, double expected)
{
  // TODO: Place tests here (sorry about the methodname)
  var actual = ...

  Assert.Equal(expected, actual);  
}

Begge attributer kræver Xunit.Extensions som har sin egen Nuget pakke (og namespace med samme navn).

Det lille fancy SQL statement er muligt fordi vi har valgt at kolonenavne skal indgå i TestArea51 området. Så slipper vi for at ændre vores test hvis vi senere ønsker at udvidere området med mere test data. Bemærk at Excel navne svarer til et område inkl. arknavnet. Man kan altså ikke have samme område navn på forskellige ark - tilgengæld skal man heller ikke angive arknavnet i attributten ovenfor.

Når du kører testen vil den blive kaldt for hver række i dit test data - også selvom nogle rækker skulle fejle. Input data fremgår i test rapport hvis det fejler, så det er nemt at finde de rækker som ikke lever op til dine krav. Vær opmærksom på at GUI test runneren godt kan kløjs lidt i det hvis alt for mange rækker fejler. Jeg har med et snuptag fået 2000+ test bare på dette data - det er ret meget output hvis alle tests fejler.

Jeg vil fremover ikke længere poste på dotninjas.dk under navnet “t4rzsan”, men i stedet bruge det lidt mere sigende navn “Jakob Christensen” Smiley.

Jeg er træt af spam.  Jeg forstår ikke pointen med spam, for det må være meget begrænset, hvad der tjenes af penge på at sende spam ud.  Nogle spam mails indeholder endda kun volapyk, så jeg har ingen idé om, hvorfor folk sender dem ud.  Det er ikke fordi, jeg får oceaner af mails, men der dukker vel omkring 5 spam mails op om dagen.

På det seneste har jeg fået lidt mere seriøse og målrettede spam mails fra østeuropa og Indien med tilbud om at indgå samarbejde om outsourcing af udviklingsarbejde.  Disse mails bærer præg af, at de ikke bare er sendt ud i flæng, og at der rent faktisk står et reelt firma bag, som beskæftiger sig med programmering.  Det skumle ved disse mails er, at de er sendt til en af mine emailadresser, hvor mit navn ikke indgår, og ikke desto mindre er disse mails adresseret til “Jakob”.  Jeg har ingen idé om, hvordan afsenderen har fundet ud af at koble mailadresse og navn sammen, og det gør mig mistænksom.

Derfor har jeg en mistanke om, at et eller andet firma et eller andet sted har solgt min emailadresse.

Inspireret af Jeff Blankenburg har jeg derfor nu opsat en catchall emailadresse på mit domæne styrdindiabetes.dk, og hver gang jeg opretter en profil ved registrering på et eller andet website, benytter jeg en emailadresse opfundet til formålet.  Emailadressen findes ikke i virkeligheden, men når man sender til den, ryger den i min catchall mailbox, og jeg kan se, hvilken adresse, der er sendt til.

Hvis jeg f.eks. skal købe noget fra www.foobar.com, oplyser jeg mailadressen foobar på domænet styrdindiabetes.dk.  På den måde kan jeg forhåbentlig se, hvem synderen er, hvis jeg en dag begynder at modtage spam på den email.

Ulempen ved en catchall er, at man risikerer spam fra folk, der bare spammer tilfældige mailadresser på domænet.

Nu vil tiden vise, om idéen holder.

Mark Seemann skrev forleden et godt indlæg om de udfordringer, man møder, når man skal mappe DTO objekter til domain objekter og videre til viewmodel objekter.

Jeg er en af dem, der har forsøgt sig med at snyde og lade DTO objekter vandre op igennem forretningslaget, og det bliver hurtigt grimt. Bare det at DTO klasserne alle har default constructor gør, at vedligeholdelse af koden bliver en pine. Mark er inde på, at mapning mellem DTO objekter og domain objekter kan være svær, fordi ORM frameworks ikke understøtter mapping, når der ikke findes en default constructor og alle propeties ikke har en setter.

Jeg har haft nogenlunde succes med at bruge AutoMapper til det. Det viser sig, at AutoMapper er i stand til at mappe properties på source objektet til constructor parametre på destination objektet. Man skal bare sørge for, at parametre og properties hedder det samme (de behøver ikke have samme casing), hvilket i mine øjne i øvrigt er god ting at have i sin kodestandard alligevel (konsistent navngivning).

Lad os tage udgangspunkt i følgende klasser Track og DbTrack (tyvstjålet fra Mark’s indlæg):

   1:  public class Track
   2:  {
   3:      private readonly int id;
   4:      private string name;
   5:      private string artist;
   6:   
   7:      public Track(int id, string name, string artist)
   8:      {
   9:          if (name == null)
  10:              throw new ArgumentNullException("name");
  11:          if (artist == null)
  12:              throw new ArgumentNullException("artist");
  13:   
  14:          this.id = id;
  15:          this.name = name;
  16:          this.artist = artist;
  17:      }
  18:   
  19:      public int Id
  20:      {
  21:          get { return this.id; }
  22:      }
  23:   
  24:      public string Name
  25:      {
  26:          get { return this.name; }
  27:          set
  28:          {
  29:              if (value == null)
  30:                  throw new ArgumentNullException("value");
  31:   
  32:              this.name = value;
  33:          }
  34:      }
  35:   
  36:      public string Artist
  37:      {
  38:          get { return this.artist; }
  39:          set
  40:          {
  41:              if (value == null)
  42:                  throw new ArgumentNullException("value");
  43:   
  44:              this.artist = value;
  45:          }
  46:      }
  47:  }
  48:   
  49:  public class DbTrack
  50:  {
  51:      public int Id { get; set; }
  52:      public string Name { get; set; }
  53:      public string Artist { get; set; }
  54:  }

Track har ikke en default constructor og har setter properties for Name og Artist, men ikke for Id. AutoMapper kan – uden nogen egentlig konfiguration – benyttes til at mappe fra DbTrack til Track på følgende måde:

   1:  Mapper.CreateMap<DbTrack, Track>();
   2:   
   3:  var source = new DbTrack()
   4:  {
   5:      Id = 12,
   6:      Name = "Name12",
   7:      Artist = "PSB"
   8:  };
   9:   
  10:  var destination = Mapper.Map<Track>(source);

Kaldet til Mapper.Map i linje 10 vil først kalde constructoren på Track med de rigtige parametre bestemt ud fra parametrenes navne, og da Track klassen har setters på to properties, vil disse properties blive kaldt efterfølgende. Hvis Track klassen kun havde getters på alle properties, ville det naturligvis kun være constructoren, der blev kaldt. Hvis Track klassen har parametre i constructoren, som har navne, der ikke kan matches med properties på DbTrack klassen, vil AutoMapper fejle med en exception.

TFS er en god ting at have (især når man tidligere brugte VSS), men da antallet af aktive udviklere lige i min afdeling (som er et aktuariat) kan tælles på en finger, er “vi” ikke storforbrugere af de avancerede TFS features med automatisk builds og what-not.  Der er derfor nok at lære, når man ind imellem støder på noget ud over det lidt mere sædvanlige som check-ins og bug reports.

Forleden løb jeg ind i problemer, da jeg havde fat i et projekt, der kun sjældent bliver rettet i.  Det viste sig, at en kollega, der ikke længere sidder i aktuariatet, havde nogle udestående pending changes på nogle filer.

En søgning ledte mig hurtigt til “tf undo” kommandoen, som giver mulighed for at lave undo på pending changes for andre brugere i andre workspaces.  Så med følgende kommando var jeg hurtigt kørende igen:

tf undo /workspace:kollegasmaskinnavn;kollegasinitialer /recursive $/Foldernavn/Projektnavn /server:TFSservernavn