Ziff Davis EnterpriseDevLife
Advertisement
Advertisement

Sunday, April 26, 2009 12:24 PM/EST

Overcoming "Fear of Unit Testing" Disease

Unit testing is one of those development practices that lives in black and white. You either use it religously and it has become second nature or you have never used it, not even once. If you are in the "never used it" camp, there's a good chance that you have been wanting to use it but found it daunting and don't know where to begin.

That's the boat I've been in for a long time. And it's been a little embarrassing.

But I have been writing tests finally and found a way to do so without diving into the deep end of the pool.

What I have been using tests for is not in an application, but for testing coding ideas that I want to work out. Some of these are to get a better undersantanding of how a feature works and others are for conference session demos.

Usually when I'm doing this type of work, I create methods inside of a console app and then have the main module call the method. Then I can debug and watch what happens.

The first question I need to ask is "what am I testing?". The answer in my scenario is that I want to get the strongly typed EntitySet name of a given EntityType. The next question is "what do I expect from the test?". In this case, I'll want to call the method, passing in the type and then verify that the returned string matches what I would expect. For example, my "Customer" entiity is part of the "MyEntities.Customers" EntitySet. An "Employee" entity that inherits from a "Person" entity would be part of the "MyEntities.People" EntitySet.

There's a really important thing to be aware of here. Much of the work I do these days revolves around Entity Framework which is tightly bound to data access. Accessing a database with unit tests is a hotly contested practice. EF does not lend itself well to "best practices" style unit testing. Most intros to Unit Testing will perform a calculation and have nothing to do with data access. But I needed to use something that is very real to me, so I found a method that needs testing that doesn't hit the database.

I can write my first test using baby steps...the Unit Test Wizard.

A more experienced author of unit tests will start by writing the test and then implementing the logic of the method that the test calls. This is called Test Driven Development (TDD). For someone like me, I'm better off starting with existing methods and using the Unit Test Wizard to create default tests to test those methods. Then I can customize the tests to achieve my goal. Eventually, I will be familiar enough with the process that I can use TDD instead.

And I do already have my method written since I was already "testing" it in a console application. THis ia a great way for me to take my baby steps.

Here is the method that I want to test.

It's signature is

//MetadataWorkspace Method Extension returns e.g., "MyEntities.Customers"
public static string GetStronglyTypedEntitySetName<TEntity>
         (
this System.Data.Metadata.Edm.MetadataWorkspace mdw)
{
 
var entityName = typeof(TEntity).Name;
 
//use MetadataWorkspace to find the name of the EntitySet that contains this entity type
 
var entContainer = mdw.GetItems<System.Data.Metadata.Edm.EntityContainer>
                              (System.Data.Metadata.Edm.
DataSpace.CSpace).First();
 
var entSets = from eset in entContainer.BaseEntitySets
 
where eset.ElementType.Name == entityName
 
select eset;
 
var containerName = entSets.First().EntityContainer.Name;
 
return containerName.Trim() + entSets.First().Name;
}

I'll start by using the Unit Test Wizard to create my test. The wizard lets me create tests in C# or VB. Because I have been trying to familiarize myself more with C# lately, I'll do it in C#.

Surely, you've noticed the "Test" menu option in Visual Studio 2008 (Professional) by now. ;-)

Select "New Test" and then "Unit Test Wizard" from the options. In the drop down at the bottom of the dialog, select "Create a new Visual C# test project..." or you can create a VB project. You can also use the wizard to add additional methods to an existing test project.

ut1.png

 

Next, you need to select the method(s) to test. My solution has a number of projects but I'm only going to select a single method for now.

ut2.png

The wizard will create a test class that contains a method to spin up a context from which to call the methods (TestContext, which you can just leave alone) and a test method to call your method.

In my case, I get this in two parts becasue my method takes a generic type. The wizard creates the actual test method (notice the TestMethod attribute) that will be run during testing. The TestMethod calls a helper method which has the code to executes my existing method that I am trying to test.

If you are testing a method that does not use generics, all of the testing code will be contained in the single TestMethod.

Here is the test method

[TestMethod()]
public void GetStronglyTypedEntitySetNameTest()
{
  GetStronglyTypedEntitySetNameTestHelper
                           <
GenericParameterHelper>();
}

The TestMethod is exposed to the unit testing. It then calls its helper method.

public void GetStronglyTypedEntitySetNameTestHelper<TEntity>()
{
 
MetadataWorkspace mdw = null; // TODO: Initialize
 
string expected = string.Empty; // TODO: Initialize
 
string actual;
  actual =
ExtensionMethods
          
.GetStronglyTypedEntitySetName<TEntity>(mdw);
 
Assert.AreEqual(expected, actual);
 
Assert.Inconclusive("Verify the correctness of this test"
                       + "method."
);
}

This default test is very close to what I want to test but it has a big gap. I will need to spin up the metadataWorkspace. Although this won't hit the database, this still forces my test to be pretty tightly bound to my application. The reason is that EF needs to read the metadata files in order to create the MetadataWorspace. There are a few ways to do this. One is to create an EntityCOnnection (an EntityCOnnection does not touch the db unless you execute a query, function or call SaveChanges, so that's no problem). ANother is to get it from an instantiated ObjectContext and the last is to read the metadata files directly.

I'm going to choose the first which will require the unit testing application to be aware of the EntityConnection string. That string is stored in the app.config file of my model's project so I will add a link to that file.

ut3.png

 

There's on other modification I need to make to the helper method. When I call it, from the TestMethod, I need to pass in the type and the expected result based on that type. Here's the revised Helper method.

public void GetStronglyTypedEntitySetNameTestHelper<TEntity>(string expectedName)
{
  System.Data.EntityClient.
EntityConnection econn =
                  
new System.Data.EntityClient.EntityConnection("Name=MyEntities");
  
MetadataWorkspace mdw = econn.GetMetadataWorkspace();
  
string expected = expectedName;
 
string actual;
  actual =
ExtensionMethods.GetStronglyTypedEntitySetName<TEntity>(mdw)
 
Assert.AreEqual(expected, actual);
}

Now I will modify the TestMethod to test a few actual real cases.

[TestMethod()]
public void GetStronglyTypedEntitySetNameTest()
{
GetStronglyTypedEntitySetNameTestHelper<EFTipsModel.
Customer>("MyEntities.Customers");
GetStronglyTypedEntitySetNameTestHelper<EFTipsModel.
SalesPerson>("MyEntities.SalesPeople");
}

Now I'll go ahead and run the test. There are a lot of ways to fire this up for example you can run all tests in the solution, all tests in a particular context or you can pick and choose which tests to run. What you don't want to do, though is run with F5. Running & debugging tests is different than running an application.

To run a single test, I find it easiest to right click on the test in the code window and select Run Tests. Even if there are multiple tests, only this one will be run.. Possibly someone with more experience will recommend another way. ut4.png

Oh surprise! The test failed.

ut5.png

But why?  It's in the Error Message column which needs to be expanded.

In this scenario, the message is short and I can see the whole thing if I hover over the message in that window. But when there is a major failure in the code I'm testing I need more than the little error. I had this problem on another test and could not figure out how to see the exception. Someone helped me out and showed me the way.

Just right click on the line and select View Test Results. Then you can see the problem. In this case, the actual result is missing the "." in between MyEntities and Customers. But if an exceptoin had been thrown in my method that I was testing, I would see those details right here.

ut6.png

Oops! But now I can go back and fix up my method to ensure that the "." is included.

Once that's done, I want to run the test again. As long as I have the test results window open I'll run it from there. Not that there is a RUN option and a DEBUG option in case you do want to step through any of your code, you'll want to choose debug.

Not so painful for a first foray into Unit Testing. I think that once you get past the first steps, you'll become addicted. 

TrackBack

TrackBack

http://blogs.devsource.com/cgi-bin/mte/mt-tb.cgi/16978

Post a Comment

 
 

Advertisement

Syndication

Subscribe: