Th3Fix3r
Th3Fix3r

Reputation:

How to avoid duplicating test code

I have written my units tests, and where external resources are needed it is dealt with by using fakes.

All is good so far. Now i' am faced with the other test phases, mainly integration where i want to repeat the unit test methods against real external resources e.g The Database.

So, What are the recommendations for structuring test projects for Unit Vs Integration testing? I understand some people prefer separate assemblies for unit and Integration?

How would one share common test code between the two assemblies? Should i create a thrid assembly which contains all the Abstract Test Classes and let the unit and integration inherit? I am looking for maximum re-usability...

I hear alot of noise about Dependency Injection (StructureMap), How could one utilise such a tool in the given Unit + Integration test setup?

can anyone share some wisdom? Thanks

Upvotes: 5

Views: 470

Answers (7)

Lutz Prechelt
Lutz Prechelt

Reputation: 39326

If the only difference between many of your unit tests and the corresponding integration tests is that the latter use "real" ressources rather than fakes (mocks), one approach is the following:

  1. Make a flag is_unit_test available to your test class from the outside
  2. In the class setup, make fake or real resources available depending on the flag. For instance if you need to use a DB API that is either real (an instance of class DBreal) or fake (an instance of class DBfake), your initialization may look like if is_unit_test then this.dbapi = new DBfake else this.dbapi = new DBreal. (DBreal and DBfake need to conform to the same interface, let's call it DBapi.)
  3. From the point of view of your test methods, step 2 amounts to (manual) Dependency Injection: The method does not know what class actually implements its dependency (the DB API). Rather, the dependency is injected into the method from the outside.
  4. Where your test cases require the DB API, they use this.dbapi
  5. Now you execute one and the same test class with the flag set for unit testing and without the flag set for integration testing. (How to make the flag available depends on your unit testing framework.)
  6. Obviously, the same approach can be used if you need more than one resource in a test class.
  7. Some people will find the explicit if in step 2 ugly. To make it more "elegant", you could employ an Inversion of Control (IoC) container (in Java for instance Spring or PicoContainer) to semi-automate the Dependency Injection instead. The initialization would then look like this.dbapi = myContainer.create(DBapi).
  8. In simple cases, an IoC container will only complicate things, because configuring the container is not trivial, involves learning, opens the possibility of a new class of mistakes, and involves additional files.
  9. In more complex cases however, the container makes things easier, because if the creation of your resources requires still other resources, the container will take care of their initialization as well and complexity would indeed go down. But unless you really get there, I suggest to KISS.
  10. Unless you have an important reason for separate assemblies, they violate KISS. I suggest to wait for that reason first.

(Note that some people may tell you that Dependency Injection is only done at the class level. I consider this unwarranted dogmatism. Injection simply means that a caller does not know the exact class it is calling, no matter how it obtained the object. It often becomes more useful when applied at the class level, but depending on your test framework this may make things overly complicated in the above case. Note that some test frameworks have their own injection capabilities, though.)

Upvotes: 0

henginy
henginy

Reputation: 2071

For code that will be executed in setup & teardown phases, the base class approach would work well. For integration tests, you can extract the functionality of your unit tests into well-parameterized non-test methods (preferably placed in another namespace) and call these "common" methods from both unit and integration tests. Putting unit tests, integration tests and common methods into separate namespaces would suffice, there would be no need for extra assemblies.

Upvotes: 1

Th3Fix3r
Th3Fix3r

Reputation:

After some experimenting this is how you can re-use test methods:

    public abstract class TestBase
    {
        [TestMethod]
        public void BaseTestMethod()
        {
            Assert.IsTrue(true);
        }
    }


    [TestClass]
    public class UnitTest : TestBase
    {
    }

    [TestClass]
    public class IntegrationTest : TestBase
    {
    }

The unit and integration test class will pick up the base class test methods and run them as two separate tests.

You should be able to create a parametised constructor on the base class to inject your mocks or resources.

I think this method can olny be used with classed within the same assembly. So it looks like the single assembly approach will have to do for now.

Thanks for the tips ppl!

Upvotes: 0

Shane Courtrille
Shane Courtrille

Reputation: 14097

On our project we have both integration and unit tests together but in separate folders. Our project layout is such that we have separate assemblies for the main sections (Domain, Services, etc). Each assembly has a matching test assembly. Test assemblies are allowed to reference other test assemblies.

This means Services.Test can reference Domain.Test which makes sense to us because Services references Domain in the actual code.

In terms of reusable pieces we have

  • Builders - These provide a fluent interface for creating the most important/complex objects in our domain. These live in the main test folder for our domain. Our domain test assembly is referenced by all other test assemblies.

  • Mothers - These insert data into the database. They give back an Id for the inserted row which can be used to load the object if required. These live in the main test folder for our services.

  • Helpers - These are guys that do small things throughout our testing. For instance we prefer to allow access to collections via IEnuermable so we have a CollectionHelper.AssertCountIsEqualTo<_T>(int count, IEnumerable<_T> collection, string message) which wraps the IEnumerable in a List and asserts a count. Our Helpers all live in a common test which every other test references.

As for an IoC container if you can use one on your project they can be a huge help not only in testing (via auto mocking) but also in general development. With the overheard of registering everything with the contain though it might be a bit much for just testing.

Upvotes: 0

Pedro
Pedro

Reputation: 12328

Whether you split the tests into two projects or keep them in one might depend on the number of classes/tests you have. Too many classes in a single project would make it difficult to dig through. If you do split them out, helper/common methods could be thrown into a third assembly, or you could make them public in the unit test assembly, and let the integration assembly reference that one. Make things only as complex as you have to.

Upvotes: 0

John Hyland
John Hyland

Reputation: 6872

One approach would be to create a separate file with helper methods that would be used across multiple testing contexts, and then include that file in both your unit tests and your functional tests. For the parts that vary, you could use dependency injection - for example, by passing in different factories. In the unit tests, the factory could build a fake object, and in the functional tests it could insert a real object in to your test database.

Upvotes: 0

Gerrie Schenck
Gerrie Schenck

Reputation: 22368

I don't think you should physically separate the two. A good solution is to put the Microsoft.TeamFoundation.PowerTools.Tasks.CategoryAttribute above your tests to identify regular and integration tests. When running tests (even with MSBuild) you can decide to run only the tests you're interested in.

Alternatively you can put them in seperate namespaces.

Upvotes: 1

Related Questions