Trainee Program part 4 - Unit Testing
Obviously in this little application we haven't taken the test-first approach. Maybe next time. For now, though, we needed a way to test our SqlDataProvider class, and NUnit seemed like a good solution.
We knocked up a new assembly which we called Qaf.TrainAlloc.Tests, and created a class therein called DataProviderTests. For starters, this class contained two tests: CanGetAllTrainees() and CanSaveTrainee(). Pretty straight forward. Here's one of 'em:
[Test] public void CanGetAllTrainees() { IDataProvider dp = new SqlDataProvider(); IList<TraineeDataContract> list = dp.GetAllTrainees(); Assert.Greater(list.Count, 0, "GetAllTrainees return no trainees"); }
So we make ourselves a new SqlDataProvider and check that when we call GetAllTrainees() it returns more than one item.
Our first run of this test failed miserably, by the way. We had forgotten to open the SqlConnection before we called ExecuteReader on the command, so an exception was thrown. Hey - that's what unit tests are for, right? We successfully proved that our code was broken!
Once we fixed up that little bug, the test passed just fine!
Next step was to test that we can save a new or existing trainee back to the database. Here's our code for that:
[Test] public void CanSaveTrainee() { TraineeDataContract tdc = new TraineeDataContract(); tdc.ID = Guid.NewGuid(); tdc.FirstName = "Fred"; tdc.LastName = "Bloggs"; tdc.IsActive = true; IDataProvider dp = new SqlDataProvider(); dp.SaveTrainee(tdc); SqlDataProvider sdp = new SqlDataProvider(); TraineeDataContract addedTdc = sdp.GetTrainee(tdc.ID); Assert.IsNotNull(addedTdc, "A trainee with the specified ID was not found!"); Assert.AreEqual(addedTdc.FirstName, tdc.FirstName, "The new trainee's first name is wrong"); Assert.AreEqual(addedTdc.LastName, tdc.LastName, "The new trainee's last name is wrong"); Assert.AreEqual(addedTdc.IsActive, tdc.IsActive, "The new trainee's active status is wrong"); tdc.FirstName = "Joe"; dp.SaveTrainee(tdc); sdp = new SqlDataProvider(); addedTdc = sdp.GetTrainee(tdc.ID); Assert.IsNotNull(addedTdc, "A trainee with the specified ID was not found!"); Assert.AreEqual(addedTdc.FirstName, tdc.FirstName, "The new trainee's first name is wrong"); }
Ok, so a quick look at that code should give you an idea of what we're doing. There's one new part in there, though - we call a method called SqlDataProvider.GetTrainee(Guid id). Where'd that come from?
Well, we cheated a bit. We needed a way to tell if our trainee had been saved correctly, so we added a GetTrainee method just to SqlDataProvider. It's not part of the IDataProvider interface, because we don't necessarily want it to be called in the final application. We could have added it to the test class, or made a helper class somewhere to do it, but what the hell.
So this class tests that we can save a new trainee, and, once saved, we can modify it. Great! One problem, though - every time we ran the test, it was creating a new trainee in our table. We didn't want that.
The solution was to add a "TestFixtureSetup" method and a "TestFixtureTearDown" method. You can think of these as constructors and destructors for tests - the setup method gets run at the start of the testing process, and the teardown method at the end.
Here they are in all their glory:
private Guid _testID; [TestFixtureSetUp] public void SetUpTests() { _testID = Guid.NewGuid(); } [TestFixtureTearDown] public void TearDownTests() { SqlDataProvider sdp = new SqlDataProvider(); sdp.DeleteTrainee(_testID); }
So we created a private Guid field called _testID, and also another helper method in SqlDataProvider called DeleteTrainee. We then used the _testID field in the CanSaveTrainee test rather than creating a new GUID each time. So now the trainee we created and modified gets deleted at the end of the testing process. Excellent!
What's next, then? Oh yeah - we still have no way to create our original Trainee objects from the TraineeDataContract objects that are coming back from the calls to IDataProvider. That's part 5!