Friday, January 29, 2016

NUnit TestCaseSource Attribute

As you've probably picked up if you've read my other posts (I'm pretty sure I said it outright), I'm a huge fan of automated unit testing.  I've used MS Test and NUnit to test my C# code and I used to think they were pretty much equal.  However, I found something in NUnit recently that has me leaning their way.  To be fair, I'm not sure whether this is possible with MS Test and I have no reason to find out (we're using NUnit at the client).

I tend to write long test names.  Like, really long.  I tell people that I prefer clarity in the name over brevity.  I've also recently been converted to the single test philosophy where a single unit test only checks one thing.  That means that if you have a complex object of a Person you'd have one test for FirstName and another test for LastName, for example.  As you can probably imagine, my unit test files were pretty big.  That has a lot of negative ramifications, not the least of which is that it's hard to find whether you've already tested something.

UPDATE: This isn't the best example.  Check out this other post for a simpler way to do this.

Enter NUnit's TestCaseSourceAttribute and iterators.  What you can do is write a single test that accepts parameters, then create an iterator-based property that runs that test with different parameters.

Let's say we have a method called GetFullName, which takes a first, middle, and last name and concatenates them to make a full name:
   1:  public string GetFullName(string firstName, string lastName, string middleName)
   2:  {
   3:      throw new NotImplementedException();
   4:  }

So we write a basic test to confirm that when we pass all three parameters we get them back as "[first] [middle] [last]".  We'll call it "GetFullName_ShouldConcatenateFirstMiddleAndLastWhenAllThreeHaveValues":
   1:  [Test]
   2:  public void GetFullName_ShouldConcatenateFirstMiddleAndLastWhenAllThreeHaveValues()
   3:  {
   4:      // arrange
   5:      var program = new Program();
   6:   
   7:      // act
   8:      var fullName = program.GetFullName("Jumping", "Flash", "Jack");
   9:   
  10:      // assert
  11:      Assert.AreEqual("Jumping Jack Flash", fullName);
  12:  }

We update the method so the test passes:
   1:  public string GetFullName(string firstName, string lastName, string middleName)
   2:  {
   3:      return string.Format("{0} {1} {2}", firstName, middleName, lastName);
   4:  }

So now we have a problem.  Our test passes, but it's only the positive test case.  This method will clearly only work the way we want if we pass all three names.  If first name is null or empty we'll end up with a leading space we don't want.  We can create more tests to handle null values and empty strings, but that's a lot of combinations of values for a really simple method.  What we can do instead is use TestCaseSource and iterators.  Here's how we'd rewrite that one test this new way:
   1:  public static IEnumerable GetFullNameTestsCases
   2:  {
   3:      get
   4:      {
   5:          yield return
   6:              new TestCaseData("Jumping", "Flash", "Jack", "Jumping Jack Flash").SetName(
   7:                  "ShouldConcatenateFirstMiddleAndLastWhenAllThreeHaveValues");
   8:      }
   9:  }
  10:   
  11:  [Test, TestCaseSource("GetFullNameTestsCases")]
  12:  public void GetFullName(string firstName, string lastName, string middleName, string expectation)
  13:  {
  14:      // arrange
  15:      var program = new Program();
  16:   
  17:      // act
  18:      var fullName = program.GetFullName(firstName, lastName, middleName);
  19:   
  20:      // assert
  21:      Assert.AreEqual(expectation, fullName);
  22:  }

What we're doing here is creating the test data in the GetFullNameTestCases property, then we're specifying where the test should get its inputs by using the TestCaseSource attribute on the test itself.  As long as the number of parameters matches the number of arguments, everything will work fine.  Furthermore, we can add additional tests really easily just by adding more yield return statements.  Let's say we wanted to test for a null first name.  We just add this to the property:
   1:  yield return
   2:      new TestCaseData(null, "Flash", "Jack", "Jack Flash").SetName(
   3:          "ShouldConcatenateFirstMiddleAndLastWhenAllThreeHaveValues");

Next time we run our test, both sets of test data will get picked up and passed separately to the test method.  This method can greatly speed up your test writing and make it easier to cover more cases.

Mocking the Database

I believe I mentioned before that I worked with a custom, home-grown ORM at one of my clients.  If I didn't mention that, I'm mentioning it now.  One of the biggest problems I had with the solution we implemented was that we didn't have any tests for it.  Every now and then we'd encounter a new scenario (like returning a list of objects instead of a single object) and we'd have to code it in, with no way to make sure we weren't breaking what was already there.

One (particularly slow) day I decided enough was enough and I set out to create automated unit tests for every method in that behemoth.  That's when I encountered the problem: how do you mock a database for consumption by ADO.NET?  It turns out it's pretty easy and pretty straightforward.  First off, I found this code somewhere else (which, I know, technically violates the name of the blog, but it is what it is).  You can check out the original post here if you're interested.  I had to make a few changes for my version of MOQ and my specific circumstances, but that's the blog that got me started down the right path.

Here's the high level overview of the ORM: go get data using a DbDataReader, read through the columns in the reader, and for each column, find a corresponding property on the object and map the value from the reader to the property.  It's honestly pretty straightforward.  All I had to figure out was how to mock a DbDataReader.  Here it is:


   1:  private Mock<DbDataReader> CreateFakeDbDataReader(int numberOfFields, int numberOfReads = 1,
   2:              MockBehavior mockBehavior = MockBehavior.Loose, bool hasRows = true)
   3:  {
   4:      // create a mock repository (database)
   5:      var repository = new MockRepository(mockBehavior);
   6:      // create a reader for the mocked repository
   7:      var moqReader = repository.Create<DbDataReader>();
   8:      // setup the reader so that it always has rows (or indicates that it has rows anyway)
   9:      moqReader.SetupGet(p => p.HasRows).Returns(hasRows);
  10:      // setup the reader to indicate it has the specified number of fields
  11:      moqReader.SetupGet(p => p.FieldCount).Returns(numberOfFields);
  12:   
  13:      // readCounter is used to allow the reader to be iterated
  14:      // incrementing readCounter in the callback allows the reader to be read a specific number of times
  15:      var readCounter = 0;
  16:      moqReader.Setup(x => x.Read()).Returns(() => readCounter < numberOfReads).Callback(() => readCounter++);
  17:   
  18:      return moqReader;
  19:  }

When I want to mock a result set that has only one row I use this:
   1:  private DbDataReader CreateDataReaderWithSingleResultSet(Dictionary<string, object> values,
   2:              int numberOfReads = 1, bool hasRows = true)
   3:  {
   4:      // get the basic fake database reader
   5:      var moqReader = CreateFakeDbDataReader(values == null ? 0 : values.Count, numberOfReads, hasRows: hasRows);
   6:   
   7:      if (values == null)
   8:      {
   9:          return moqReader.Object;
  10:      }
  11:   
  12:      // iterate the objects (fake data)
  13:      for (var i = 0; i < values.Count; i++)
  14:      {
  15:          var item = values.ElementAt(i);
  16:          // setup the reader to return the name of the "field" when the index is used on GetName
  17:          moqReader.Setup(p => p.GetName(i)).Returns(item.Key);
  18:          // setup the reader to return the value when it encounters the key
  19:          // this is where we specify that if reader["FirstName"] is evaluated, "Fake" (or whatever) will be returned
  20:          moqReader.SetupGet(p => p[item.Key]).Returns(item.Value);
  21:          moqReader.SetupGet(p => p[i]).Returns(item.Value);
  22:      }
  23:   
  24:      return moqReader.Object;
  25:  }

And when I want to mock a result set that has multiple rows I use this:

   1:  private DbDataReader CreateDataReaderWithSingleResultSetWithMultipleRows(List<Dictionary<string, object>> rows,
   2:              bool hasRows = true)
   3:  {
   4:      // get the basic fake database reader
   5:      var moqReader = CreateFakeDbDataReader(rows.First().Count, rows.Count, hasRows: hasRows);
   6:   
   7:      var currentRow = 0;
   8:   
   9:      // iterate through the "rows" of data
  10:      for (var i = 0; i < rows.Count; i++)
  11:      {
  12:          // for each "row" in the data, check if the current row is being retrieved
  13:          if (i != currentRow)
  14:          {
  15:              continue;
  16:          }
  17:   
  18:          var row = rows[i];
  19:          // iterate through the "fields" in the current row
  20:          for (var j = 0; j < row.Count; j++)
  21:          {
  22:              var item = row.ElementAt(j);
  23:              // setup the reader to return the name of the "field" when the index is used on GetName
  24:              moqReader.Setup(p => p.GetName(j)).Returns(item.Key);
  25:              // setup the reader to return the value when it encounters the key
  26:              // this is where we specify that if reader["FirstName"] is evaluated, "Fake" (or whatever) will be returned
  27:              moqReader.SetupGet(p => p[item.Key]).Returns(item.Value);
  28:          }
  29:      }
  30:   
  31:      return moqReader.Object;
  32:  }

The one part that I never got coded because I didn't really need to was returning multiple result sets.  I'm sure it can be done, but I haven't had to do it yet.  Happy coding!

Monday, January 18, 2016

Boot Camp

Let me start this post by saying that I'm not an Apple person.  I have a Windows phone (though I hate it; I prefer Android, but this one was cheaper) and Windows PCs at home and work.  I'm primarily a .NET developer (which you'll note is a Microsoft product).  I don't hate Macs (well, I didn't before this); I just don't use them.  They're more expensive than PCs and I don't know them as well as I know PCs.  I've had a PC since back when they were called IBM compatibles.

TL;DR: Boot Camp Assistant is probably great, but on old machines it sucks.  Snow Leopard with Boot Camp Assistant 3.2 is not as user friendly as newer versions probably are.  A Macbook Pro manufactured in 2009 cannot boot from a thumb drive.  When all else fails, just let the computer do it's thing.

All that said, I married an Apple person.  Everyone in her family is an Apple person.  I knew it when we decided to get married, but I didn't think it would be the one thing that finally made me crazy.  My mother-in-law got a new job, which requires her to have Windows on her home computer.  She has a Mac.  No problem, though, right?  Using Boot Camp Assistant, Mac will guide you through the painless process of dual booting OS X and Windows on the same machine.

I verified the specs, double-checked everything and got started.  It is now three days later and I'm almost finished.  Let me walk you through my troubles in the hopes that my trials and tribulations may help you in a similar situation.

I started off by launching Boot Camp Assistant and trying to download the drivers, as the instructions from Apple say to do.  No joy there.  A message came up that no drivers were found or something.  I honestly don't remember now; that was two days ago.  OK, no big deal.  A quick search of the Internet found the instructions that say I should just skip that part.  Got it.  Moving on then.  The next part of the wizard creates the partition for Windows.  At this point I'm happy.  I just want to make that clear.  At this point everything seems to be going according to plan.  The Chiefs are finally starting to put up a fight against the Patriots and it appears for the moment as though Apple's instructions will work fine.

But no.  Boot Camp Assistant can't create the partition because "some files could not be moved".  I find a video on YouTube where a guy explains how to get around it by running (what appear to be) some Unix commands.  That doesn't work either.  Some more searching of the Web and I find a solution to create a backup, wipe the drive, restore from backup and try again.  Well, that's not going to work for me.  I don't have an external hard drive to which to backup the OS and I don't particularly want to wipe the drive anyway.  I text the MIL and tell her no can do, boss.  She says I should go ahead and wipe it and reinstall.  Apparently she had anticipated this would happen and had backed everything up except her pictures.

OK.  Copy the pictures to a thumb drive, wipe the hard drive, copy the pictures back.  Got it.  How do I copy the pictures to the thumb drive?  It is now 11:30 PM on day 2.  I'm tired.  I'm falling asleep.  My wife is asleep next to me on the couch.  I want to quit.  OK.  Back to the Internet.  Ah, I need to "export" the files from iPhoto to the thumb drive.  Got it.  That was easy.  Time to wipe the hard drive.  Put the disc in and... hey, look at that... something went like it was supposed to.  Sweet.  I let it finish wiping and call it a night (although it is technically morning).

Surprisingly the reinstall goes pretty smoothly.  I will give Apple credit on this one: their install process is much faster than Windows (or so I thought at the time).  Great!  OK, OS X is installed.  Run Boot Camp Assistant again, partition the hard drive... and I'm done with the Mac side of things!  Woohoo!!

Getting to this point was supposed to be the hard part.  I had never done that stuff before.  From here on out it's just a matter of installing Windows, which I've done roughly a billion times.  Sweet.  I've got my bootable USB with a 32-bit copy of Windows 7 on it.  I've tested it to make sure it registers properly on my computer and we're good to go.  Put the thumb drive in when prompted for the disc and... Boot Camp Assistant doesn't see it.  Nor does it have any way to point to it.  Back to the Internet.  I see that I can install rEFIt and that should solve the problem.  Did that.  No joy.

After a lot more searching and a little bit of crying I decide I'll need to burn a DVD with Windows on it.  I don't have any blank DVDs.  To Walmart!  Back.  I make a bootable DVD.  I put it in the drive on the Mac.  I reboot the Mac and... nothing happens.  It just boots into OS X.  Oh, I forgot to hold the option key while it was booting.  No problem.  Reboot, hold option key (after the chime) and I get an option to boot to the Windows CD!  WOOOHOOO!!!  Choose that option and I'm home free... hahahahahahahahahahaha... no, that's not how it goes.

I end up on a black screen with a blinking cursor.  I wait.  I wait.  I wait.  I cry.  I wait some more.  I decide waiting isn't working.  Back to the Internet.  First suggestion: just wait.  Seriously.  Reboot into OS X.  Run Boot Camp Assistant.  Try to use the wizard.  It looks like it works, but I'm not going to get excited.  No.  I'm not going to.  Nope.  Nice try, Mac.  OK, what's this?  It looks like it's working!?  YES!!!!   Oh, wait, actually no.  It didn't work.

Reboot, hold option, choose Windows, black screen with blinking cursor, cry, yell, cry more, power down, walk away, drink, drink more, yell more, cry a little bit more, boot computer, forget to hold option... Windows is loading.  Holy crap.  No, this time I'm really not going to get excited.  I'm just going to stare at this screen through now-bloodshot eyes and wonder when this nightmare will end.  Wait, what's this?  It wants a product key?  I'm done?  HOLY CRAP IT WORKED!  I'M BRILLIANT!