Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I avoid writing duplicate Unit Tests for classes that implement the same interface?

I'm just getting into unit tests using Visual Studio 2010. I'm wondering if there is a workflow that would allow me to create a series of tests that apply to an Interface and then apply those interface tests to any and all classes that implement the interface.

For example say I have the following interface.

public interface IAnimal
{
   public string Name {get;}
   public string Speak();
}

I could obviously have a number of classes that implement this interface.

public class Dog:IAnimal
{
  public string Name {get{return "Dog";}}
  public string Speak{return "BARK BARK";}
  public bool LickBalls();
}

public class Cat:IAnimal
{
  public string Name {get{return "Cat";}}
  public string Speak{return "MEOW MEOW";}
  public bool Scratch();
}

So I'd like to define a series of tests that apply to all IAnimals

   public TestAnimalName(IAnimal animal)
   {      
       Assert.IsFalse(string.IsNullorEmpty(animal.Name));
   }

   public TestAnimalSpeak(IAnimal animal)
   {      
       string sound = animal.Speak();
       Assert.IsFalse(string.IsNullOrEmpty(sound));
   }

Then I'd like to define a master TestAnimal() method which could be used to test any IAnimal.

TestAnimal(IAnimal animal)
{
    TestAnimalName(animal);
    TestAnimalSpeak(animal);
}

I could then call this TestAnimal() method when testing a concrete type of IAnimal.

[TestMethod]
TestCat()
{
    Cat c = new Cat();
    TestAnimal(c);
}

[TestMethod]
TestDog()
{
    Dog c = new Dog();
    TestAnimal(c);
}

Yet when I try to do this in Visual Studio the Asserts in the called methods are ignored. I tried to simplify the problem with the method below and found that it passed even though it calls a method that should result in a failure.

[TestMethod]
public void AssertInCalledMethod() //this will pass
{
    Assert.IsTrue(true);
    Blah();
}

public void Blah()
{
    Assert.IsTrue(false);
}

So how can I avoid writing a lot of duplicate tests for classes that all implement the same interface?

like image 321
Eric Anastas Avatar asked Nov 10 '10 22:11

Eric Anastas


People also ask

Is code duplicate in unit tests acceptable?

From my personal experience, code duplication in unit tests is not only acceptable but desirable. This is not a dogma or absolute truth. Here I am only sharing what usually works for me personally and why. It may not be the same for everybody. Each of us has unique background, coding style and screen wallpaper. Anybody home?

Is it possible to write code without unit testing?

When code is tightly coupled, it can be difficult to unit test. Without creating unit tests for the code that you're writing, coupling may be less apparent. Writing tests for your code will naturally decouple your code, because it would be more difficult to test otherwise.

Why do you prefer the last approach to unit testing?

I prefer the last approach because it makes the unit test easier to read and understand. When I check this unit test with my eyes it is obvious what it does. The test ensures that str is 'Hello world'. And it is easy to notice a problem in this test, because it is so simple.

What verifies the Code of a unit test?

It is the unit test that verifies its ‘code’ and not the other way around. The ‘code’ does not verify its unit test. Ok, then what verifies the unit test? The answer is - nothing. There is nothing that checks if our unit test is bug-free. Except if we decide to write a unit test for our unit test.


2 Answers

Greg Young's Hei Hei Grensesnitt takes a slightly different approach to this problem, which I like. You can write an interface specification:

[InterfaceSpecification] 
public class ICanAddTests : AppliesTo<ICanAdd>{ 

    [Test] 
    public void can_add_two_numbers() { 
          Assert.AreEqual(5, sut.Add(2,3)); 
    } 
}

... and the framework automatically executes the test for all classes it finds that implement the interface. This is using NUnit, not MSTest, but I think it's a rather clever idea and makes for a very easy workflow.

like image 115
OdeToCode Avatar answered Sep 22 '22 13:09

OdeToCode


Your final code snippet:

[TestMethod]
public void AssertInCalledMethod() //this will pass
{
    Assert.IsTrue(true);
    Blah();
}

public void Blah()
{
    Assert.IsTrue(false);
}

fails like it's meant to on my computer. Sometimes visual studio can get confused about old dlls and run old versions of your tests (or libraries being tested), if you rename the test method does that rename reflect itself in the test runner?

P.S. I think your approach with TestAnimal(IAnimal animal) is ideal...

like image 31
Rob Fonseca-Ensor Avatar answered Sep 19 '22 13:09

Rob Fonseca-Ensor