views:

198

answers:

5

I have been making a little toy web application in C# along the lines of Rob Connery's Asp.net MVC storefront.

I find that I have a repository interface, call it IFooRepository, with methods, say

IQueryable<Foo> GetFoo();
void PersistFoo(Foo foo);

And I have three implementations of this: ISqlFooRepository, IFileFooRepostory, and IMockFooRepository.

I also have some test cases. What I would like to do, and haven't worked out how to do yet, is to run the same test cases against each of these three implementations, and have a green tick for each test pass on each interface type.

e.g.

[TestMethod]
Public void GetFoo_NotNull_Test()
{
   IFooRepository repository = GetRepository();
   var results = repository. GetFoo();
   Assert.IsNotNull(results);
}

I want this test method to be run three times, with some variation in the environment that allows it to get three different kinds of repository. At present I have three cut-and-pasted test classes that differ only in the implementation of the private helper method IFooRepository GetRepository(); Obviously, this is smelly.

However, I cannot just remove duplication by consolidating the cut and pasted methods, since they need to be present, public and marked as test for the test to run.

I am using the Microsoft testing framework, and would prefer to stay with it if I can. But a suggestion of how to do this in, say, MBUnit would also be of some interest.

+1  A: 

In MbUnit, you might be able to use the RowTest attribute to specify parameters on your test.

[RowTest]
[Row(new ThisRepository())]
[Row(new ThatRepository())]
Public void GetFoo_NotNull_Test(IFooRepository repository)
{
   var results = repository.GetFoo();
   Assert.IsNotNull(results);
}
Jon Limjap
good answer, but if I used NUnit and RowTest, I would use an enum in the rowtest, like in Rick's answer. Essentially, he's doing a RowTest the hard way.
Anthony
Well, I posted my answer ahead of Rick's. so I have no idea what he was doing.
Jon Limjap
+1  A: 

If you have your 3 copy and pasted test methods, you should be able to refactor (extract method) it to get rid of the duplication.

i.e. this is what I had in mind:

private IRepository GetRepository(RepositoryType repositoryType)
{
    switch (repositoryType)
    {   
          case RepositoryType.Sql:
          // return a SQL repository
          case RepositoryType.Mock:
          // return a mock repository
          // etc
    }
}

private void TestGetFooNotNull(RepositoryType repositoryType)
{
   IFooRepository repository = GetRepository(repositoryType);
   var results = repository.GetFoo();
   Assert.IsNotNull(results);
}

[TestMethod]
public void GetFoo_NotNull_Sql()
{
   this.TestGetFooNotNull(RepositoryType.Sql);
}

[TestMethod]
public void GetFoo_NotNull_File()
{
   this.TestGetFooNotNull(RepositoryType.File);
}

[TestMethod]
public void GetFoo_NotNull_Mock()
{
   this.TestGetFooNotNull(RepositoryType.Mock);
}
RickL
Ok, that makes sense. I'm not sure that it's the best way, but it is a way.
Anthony
It's OK until there are lots of tests within the suite, then you get a lot of repetition.
belugabob
+1  A: 

Create an abstract class that contains concrete versions of the tests and an abstract GetRepository method which returns IFooRepository. Create three classes that derive from the abstract class, each of which implements GetRepository in a way that returns the appropriate IFooRepository implementation. Add all three classes to your test suite, and you're ready to go.

To be able to selectively run the tests for some providers and not others, consider using the MbUnit '[FixtureCategory]' attribute to categorise your tests - suggested categories are 'quick' 'slow' 'db' 'important' and 'unimportant' (The last two are jokes - honest!)

belugabob
A: 
[TestMethod]
public void GetFoo_NotNull_Test_ForFile()
{   
   GetFoo_NotNull(new FileRepository().GetRepository());
}

[TestMethod]
public void GetFoo_NotNull_Test_ForSql()
{   
   GetFoo_NotNull(new SqlRepository().GetRepository());
}


private void GetFoo_NotNull(IFooRepository repository)
{
  var results = repository. GetFoo();   
  Assert.IsNotNull(results);
}
Hallgrim
A: 

To Sum up, there are three ways to go:

1) Make the tests one liners that call down to common methods (answer by Rick, also Hallgrim)

2) Use MBUnit's RowTest feature to automate this (answer by Jon Limjap). I would also use an enum here, e.g.

[RowTest]
[Row(RepositoryType.Sql)]
[Row(RepositoryType.Mock)]
public void TestGetFooNotNull(RepositoryType repositoryType)
{
   IFooRepository repository = GetRepository(repositoryType);
   var results = repository.GetFoo();
   Assert.IsNotNull(results);
}

3) Use a base class, answer by belugabob
I have made a sample based on this idea

public abstract class TestBase
{
    protected int foo = 0;

    [TestMethod]
    public void TestUnderTen()
    {
        Assert.IsTrue(foo < 10);
    }

    [TestMethod]
    public void TestOver2()
    {
        Assert.IsTrue(foo > 2);
    }
}

[TestClass]
public class TestA: TestBase
{
    public TestA()
    {
        foo = 4;
    }
}

[TestClass]
public class TestB: TestBase
{
    public TestB()
    {
        foo = 6;
    }
}

This produces four passing tests in two test classes.
Upsides of 3 are:
1) Least extra code, least maintenance
2) Least typing to plug in a new repository if need be - it would be done in one place, unlike the others.

Downsides are:
1) Less flexibility to not run a test against a provider if need be
2) Harder to read.

Anthony