Data Generation, TDD Like

On the tail of my recent additions to the data generator app I?ve been building I fell right into a need for the middle name to be generated along with standard names.  Here?s how I went about fixing this situation up with a sling at TDD & such.

The first thing I did was a quick refactor, to change the previous FullName class I had been using in my previous Part 1, Part 2, and Part 3 examples to FirstLastAmericanName.  Next I added the first test for the new class I?ll create right afterward.  The test looked like this.

   1:  [TestMethod]
   2:  public void VerifyFirstMiddleLastName()
   3:  {
   4:      var name = new FirstMiddleLastAmericanName();
   5:      Assert.IsTrue(name.FirstName.Length == 0);
   6:      Assert.IsTrue(name.LastName.Length == 0);
   7:      Assert.IsTrue(name.MiddleName.Length == 0);
   8:  }

The test, of course doesn?t build, let alone run.  So I jumped into this new class and created the following.

   1:  namespace Generator.Core.Model
   2:  {
   3:      public class FirstMiddleLastAmericanName : IFullName
   4:      {
   5:          public FirstMiddleLastAmericanName()
   6:          {
   7:              FirstName = string.Empty;
   8:              LastName = string.Empty;
   9:              MiddleName = string.Empty;
  10:          }
  11:  
  12:          public string MiddleName;
  13:  
  14:          #region IFullName Members
  15:  
  16:          public string FirstName { get; set; }
  17:  
  18:          public string LastName { get; set; }
  19:  
  20:          #endregion
  21:      }
  22:  }

So now I get a green light and I head right into the next test.

   1:  [TestMethod]
   2:  public void VerifyFirstMiddleLastNameObjectReturnsFromFactory()
   3:  {
   4:      var factory = new NameFactory();
   5:      var name = factory.Build(NameFactory.NameCulture.AmericanFirstMiddleLast) as FirstMiddleLastAmericanName;
   6:      Assert.IsTrue(name.FirstName.Length > 0);
   7:      Assert.IsTrue(name.LastName.Length > 0);
   8:      Assert.IsTrue(name.MiddleName.Length > 0);
   9:  }

I needed to then add the NameCulture enumeration.  This I added directly inside of the NameFactory.  I reviewed the other classes and name spaces I had setup.  At this time it seemed like the best place to put the enumeration.

   1:  namespace Generator.Core.Factories
   2:  {
   3:      public class NameFactory
   4:      {
   5:          public enum NameCulture
   6:          {
   7:              American,
   8:              AmericanFirstMiddleLast,
   9:              British,
  10:              French,
  11:              Japanese
  12:          }

Make note that is a very partial code segment. Also make note, I?m not adding anything except functionality right now for that second value AmericanFirstMiddleLast. I left off the rest of the class as it will be changing a lot and I?ll copy in each part as I change it. In this entry I?ll be refactoring so that the enumeration is a legitimately used part of the code base and how it reverberates through. The next thing was to get at least a basic method to get a green light for the test listed. What I ended up with to get to that point is as follows.

   1:  public IFullName Build(NameCulture nameCulture)
   2:  {
   3:      var name =
   4:          new FirstMiddleLastAmericanName
   5:              {
   6:                  FirstName = "test",
   7:                  MiddleName = "test",
   8:                  LastName = "test"
   9:              };
  10:  
  11:      return name;
  12:  }

Now it?s time to get some real randomness in the names.  I first wrote out a test.

   1:  [TestMethod]
   2:  public void VerifyFirstMiddleLastNameIsRandom()
   3:  {
   4:      var factory = new NameFactory();
   5:      var nameOne = factory.Build(NameFactory.NameCulture.AmericanFirstMiddleLast) as FirstMiddleLastAmericanName;
   6:      var nameTwo = factory.Build(NameFactory.NameCulture.AmericanFirstMiddleLast) as FirstMiddleLastAmericanName;
   7:  
   8:      Assert.AreNotEqual(nameOne.FirstName, nameTwo.FirstName);
   9:      Assert.AreNotEqual(nameOne.MiddleName, nameTwo.MiddleName);
  10:      Assert.AreNotEqual(nameOne.LastName, nameTwo.LastName);
  11:  }

This got me to a stage where I actually have a good test that will make sure I?m getting solid random names back.  After this test, with the red light received, I set out and applied these changes the Build method I created above.

   1:  public IFullName Build(NameCulture nameCulture)
   2:  {
   3:      var rand = new Random(DateTime.Now.Millisecond);
   4:      var entities = new GeneratorEntities();
   5:  
   6:      List<Names> firstNames = (from name in entities.Names
   7:                                where name.Type == "Female First Names" || name.Type == "Male First Names"
   8:                                select name).ToList();
   9:      List<Names> lastNames = (from name in entities.Names
  10:                               where name.Type == "Last Names"
  11:                               select name).ToList();
  12:      List<Names> middleNames = new List<Names>();
  13:  
  14:      middleNames.AddRange(firstNames);
  15:      middleNames.AddRange(lastNames);
  16:  
  17:      var firstMiddleLastAmericanName =
  18:          new FirstMiddleLastAmericanName
  19:              {
  20:                  FirstName = firstNames[rand.Next(0, firstNames.Count)].Name,
  21:                  MiddleName = middleNames[rand.Next(0, middleNames.Count)].Name,
  22:                  LastName = lastNames[rand.Next(0, lastNames.Count)].Name
  23:              };
  24:  
  25:      return firstMiddleLastAmericanName;
  26:  }

That should give me a full range of middle names.  Next step, that?s right, write a test.  After this test I noticed something though?

   1:  [TestMethod]
   2:  public void VerifyMultitudesFirstMiddleLastNameIsRandom()
   3:  {
   4:      var factory = new NameFactory();
   5:      var list =
   6:          factory.Build(NameFactory.NameCulture.AmericanFirstMiddleLast, 1000) as
   7:          List<FirstMiddleLastAmericanName>;
   8:  
   9:      for (int i = 0; i < list.Count; i++)
  10:      {
  11:          int compareUp = i + 1;
  12:          if (compareUp == list.Count)
  13:          {
  14:              compareUp = 0;
  15:          }
  16:          Assert.AreNotEqual(
  17:              list[compareUp].FirstName + list[compareUp].MiddleName + list[compareUp].LastName,
  18:              list[i].FirstName + list[compareUp].MiddleName + list[i].LastName);
  19:      }
  20:  }

Yeah, I needed to do some serious refactoring.  With this test, not even building, I jumped into resolving the Build methods.  During each of these stages of the refactor I made a point to run all of my tests, except the one above.  This way I assured I didn?t break anything else.  My final NameFactory code looked like what is below.  Take a good look at the following code, and below I?ll have listed a few of the major refactor changes I made.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using Generator.Core.Model;
   5:  
   6:  namespace Generator.Core.Factories
   7:  {
   8:      public class NameFactory
   9:      {
  10:          public NameFactory()
  11:          {
  12:              entities = new GeneratorEntities();
  13:              random = new Random(DateTime.Now.Millisecond);
  14:              firstNames = new List<Names>();
  15:              lastNames = new List<Names>();
  16:              middleNames = new List<Names>();
  17:              namesList = (from name in entities.Names select name).ToList();
  18:          }
  19:  
  20:          private readonly GeneratorEntities entities;
  21:          private readonly Random random;
  22:          private List<Names> firstNames;
  23:          private List<Names> lastNames;
  24:          private readonly List<Names> middleNames;
  25:          private readonly List<Names> namesList;
  26:  
  27:          public enum NameCulture
  28:          {
  29:              American,
  30:              AmericanFirstMiddleLast,
  31:              British,
  32:              French,
  33:              Japanese
  34:          }
  35:  
  36:          public IFullName Build(NameCulture nameCulture)
  37:          {
  38:              return CreateNames(nameCulture, 1)[0];
  39:          }
  40:  
  41:          public IFullName Build()
  42:          {
  43:              return CreateNames(NameCulture.American, 1)[0];
  44:          }
  45:  
  46:          public List<IFullName> Build(NameCulture nameCulture, int numberOfNames)
  47:          {
  48:              return CreateNames(nameCulture, numberOfNames);
  49:          }
  50:  
  51:          private List<IFullName> CreateNames(NameCulture nameCulture, int numberOfNames)
  52:          {
  53:              firstNames = GetFirstNames();
  54:              lastNames = GetLastNames();
  55:  
  56:              switch (nameCulture)
  57:              {
  58:                  case NameCulture.American:
  59:                      var firstLastAmericanName = new List<IFullName>();
  60:  
  61:                      for (int i = 0; i < numberOfNames; i++)
  62:                      {
  63:                          firstLastAmericanName.Add(MapName(new FirstLastAmericanName()));
  64:                      }
  65:  
  66:                      return firstLastAmericanName;
  67:  
  68:                  case NameCulture.AmericanFirstMiddleLast:
  69:                      middleNames.AddRange(firstNames);
  70:                      middleNames.AddRange(lastNames);
  71:  
  72:                      var americanFirstMiddleLastNames = new List<IFullName>();
  73:  
  74:                      for (int i = 0; i < numberOfNames; i++)
  75:                      {
  76:                          var fullName = new FirstMiddleLastAmericanName();
  77:                          fullName = MapName(fullName) as FirstMiddleLastAmericanName;
  78:                          fullName.MiddleName = middleNames[random.Next(0, middleNames.Count)].Name;
  79:                          americanFirstMiddleLastNames.Add(fullName);
  80:                      }
  81:  
  82:                      return americanFirstMiddleLastNames;
  83:  
  84:                  default:
  85:                      throw new ArgumentOutOfRangeException("nameCulture");
  86:              }
  87:          }
  88:  
  89:          private IFullName MapName(IFullName name)
  90:          {
  91:              name.FirstName = firstNames[random.Next(0, firstNames.Count)].Name;
  92:              name.LastName = lastNames[random.Next(0, lastNames.Count)].Name;
  93:              return name;
  94:          }
  95:  
  96:          private List<Names> GetFirstNames()
  97:          {
  98:              return (from name in namesList
  99:                      where name.Type == "Female First Names" || name.Type == "Male First Names"
 100:                      select name).ToList();
 101:          }
 102:  
 103:          private List<Names> GetLastNames()
 104:          {
 105:              return (from name in namesList
 106:                      where name.Type == "Last Names"
 107:                      select name).ToList();
 108:          }
 109:      }
 110:  }

The first thing I did was create the GetFirstNames & GetLastNames methods to clean up some of the junk that was in the Build methods.  Immediately I realized that I could make it even more performing by removing the actual queries against the database by simple returning the whole table into memory ? take a look at the last line of the constructor for that.  I completed that clean up, with appropriate lists being created.  Another thing I added was the mapper method.  I?m not 100% sure I like that method, but it sort of cleaned up things a little, but in turn it just doesn?t seem as elegant as it should be.

Overall I got it down to a single hit against the database & more in memory movements of the lists.  Also setup several private properties.  After a minute I thought, ?I?ll toss those setup some readonly members and save a bit of memory??  So I tried that and it worked out well.  I went through a number of other refactor steps and ended up with what I have listed above.  Next task was to see if my test would work.  So I uncommented my test and tried to run it.  It blew up with exceptions, so I dug in.

I immediately realized I had to do a little last minute refactor of my test also.  My final test ended up list this, which it appears to be, a bit more readable.  I might follow up though, with an entry cleaning this up even more.

   1:  [TestMethod]
   2:  public void VerifyMultitudesFirstMiddleLastNameIsRandom()
   3:  {
   4:      var factory = new NameFactory();
   5:      List<IFullName> list = factory.Build(NameFactory.NameCulture.AmericanFirstMiddleLast, 100);
   6:  
   7:      for (int i = 0; i < list.Count; i++)
   8:      {
   9:          int compareUp = i + 1;
  10:          if (compareUp == list.Count)
  11:          {
  12:              compareUp = 0;
  13:          }
  14:  
  15:          var fullName = list[compareUp] as FirstMiddleLastAmericanName;
  16:          var fullNameBase = list[i] as FirstMiddleLastAmericanName;
  17:  
  18:          Assert.AreNotEqual(fullName.FirstName + fullName.MiddleName + fullName.LastName,
  19:              fullNameBase.FirstName + fullNameBase.MiddleName + fullNameBase.LastName);
  20:      }
  21:  }

Hope that was useful and happy Presidents day at ya.  Fini.