Unit Testing - It's not what most of you think ...

by Donn Felker 19. September 2008 08:37

Unit Testing

Purpose: To test an individual unit of code to ensure that it is working properly.

This is where the most confusion lies, so I'm going to focus the majority of the beginning series on this area. Possibly to the effect of a few posts.

Normally you'll hear a developer say "I have unit tests for this" but in reality the tests are actually integration tests. 

At a very basic level integration testing is the combining of two units that have already been tested into a component where the interface between them is tested. In this sense a component is an aggregate of more than one unit.

Basically - if you're code has outside dependencies, and you're testing the main calling interface (maybe a method), you are probably testing the entire stack beneath the method, in turn testing how the code integrates together. AKA: Integration testing. A full post on integration tests is coming soon...

The trouble comes with understanding how to test individual units of code without testing their dependencies. Code that has tight coupling suffers from problematic testing and is very brittle and challenging to change.

Here is an example snippet of a system via a diagram for reference:

image

At a high level, here's how the application works:

image

Lets look at some legacy code that every single one of us have written at some point in our lives. This will be the code that we are trying to test. This code is located inside of the "CustomerService" shown above.

        public void SaveCustomer(Customer customer)
        {
            customer.EditDate = DateTime.Now;
            var repository = new CustomerRepository();
            repository.Update(customer);
        }

 

Can you write a unit test for this?  No. (Well, that's not 100% true. You can, kind of. But you'd have to be using a tool like TypeMock to do so - which in this exact example would be cheating in regards to OOP in this instance. More on this at a later time.) In this case - to ensure a unit test was secured around this code you'd have to do the following:

  1. New up an instance of Customer Service
  2. Create a customer
  3. Ensure the CustomerRespository Works (who knows what that means, maybe you didn't write it)
  4. Be sure that everything the customer repository touches in the "Update" method works as well (again, who knows what it does, maybe you're not the developer who wrote it)

The code for the above would look something like this:

        [Test]
        public void UpdateCustomeShouldCallUpdateOnResponsitory_Legacy()
        {
            CustomerRepository repository = new CustomerRepository();
            CustomerService service = new CustomerService(repository);
            Customer customer = new Customer();
            service.SaveCustomer(customer);
        }

 

At this point you've already introduced too many dependencies into your code. If any of these outside dependencies break - our test breaks. In this case our outside dependency is the CustomerRepository. These outside dependencies could be a network connection, a file, a database, a web service, anything. Those are all things we mare NOT trying to test. We're trying to TEST one unit of code, the update call on the repository. I'm wanting to make sure that the customer repositories "update" method gets called with the customer inside of it. I don't actually want the repository code to be called thought. I just want to know that it was called. That's all I care about. I'll write another test to ensure that the value of the customer's edit date was updated properly at a later time. I'll write more tests to ensure that the customer repository works the way it should.

 

How can we fix this?

We can test this unit of code if we follow some basic OOP principles. Mainly dependency injection.

Here's the code refactored. It is far from perfect, but this code will allow you to test in isolation of outside dependencies:

public class CustomerService : ICustomerService
{
    private ICustomerRepository customerRepository;

    public CustomerService(ICustomerRepository customerRepository)
    {
        this.customerRepository = customerRepository;
    }
    public void SaveCustomer(ICustomer customer)
    {
        customer.EditDate = DateTime.Now;
        customerRepository.Update(customer);
    }
}
In this case we can inject our dependencies (customer repository and customer) as interfaces. In the test through the use of a mocking framework we can then mock out and stub our calls to the customer repository and call expectations on them. The test would look like this:
    [TestFixture]
    public class CustomerServiceTests
    {
        MockRepository mockery = new MockRepository();
        private ICustomerRepository customerRepositoryMock;
        private CustomerService customerService;
        private ICustomer customerStub;

        [SetUp]
        public void Setup()
        {
            customerStub = mockery.Stub<ICustomer>();
            customerRepositoryMock = mockery.DynamicMock<ICustomerRepository>();
        }


        [Test]
        public void UpdateCustomerShouldCallUpdateOnRepository()
        {
            With.Mocks(mockery)
            .Expecting(delegate
            {
                Expect.Call(delegate { customerRepositoryMock.Update(null); })
                .IgnoreArguments().Repeat.AtLeastOnce();
            })
            .Verify(delegate
            {
                customerService = new CustomerService(customerRepositoryMock);
                customerService.SaveCustomer(customerStub);
            });
        }
    }

At this point we're only testing one unit of operation, therefore this is a "unit test".

Testing code that is similar to the legacy code shown above is not going to be a unit test. If you were to wrap a test around the legacy code you would be creating an integration test.

It will "look" like a unit test but what its doing is actually integration testing. This is because you would be inferring that all the other pieces of the code are going work as expected.

This is not necessary true. Trust me ,I've been down this road.

 

Problems arise when you begin to make these assumptions about your dependencies. In my experience I've had the following happen:

  • The database is down - TEST FAILS
  • The database is in an unknown state - TEST FAILS
  • The network is down - TEST FAILS
  • The file is not available - TEST FAILS
  • The file is locked - TEST FAILS
  • The file is read only - TEST FAILS
  • The SMTP Server is not up and running - TEST FAILS
  • The web service is not responding - TEST FAILS

 

All of these instances also bring up good problematic areas that you should test for. Utilizing proper OOP and Unit tests will allow you to simulate these events so that you can code for them in your application.

 

 

Review

 

What we are not doing here:

  • We are not testing the whole stack from top to bottom.
  • We are not testing two classes together.

 

What we are doing here:

  • Testing one logical piece of code.
  • Executing a Unit Test.

 

Popular Testing Frameworks:

Popular Mocking Frameworks:

 

Recommended Reading:

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

TDD | Testing

Testing For Developers Series

by Donn Felker 19. September 2008 08:25

Over the next few weeks I'm going to cover some topics will help alleviate the pain of understanding the difference between different tests in application development.

The series will consist of the following articles:

Disclaimer: I've had to explain this so many times that I figured I might as well type it out on my blog and send people the link to save myself time and to give friends a place to bookmark for reference. Also, these topics carry quite the heavy controversial and opinionated crowd - to the point where some people have vastly different opinions on what each of these topics actually mean (and some people WILL disagree with me and I'm ok with that). These are my opinions that I have gleaned from my years in the field utilizing various testing methodologies.

As a developer, when you're first getting into testing your code via a testing framework you will start to notice that testing terms are thrown around like they're ingredients in code soup. Unit, integration, functional, acceptance, regression, load, etc... WHOA, hold the phone buddy. What the ...?!?! Confusion sets in immediately. You probably have an idea of "what" you want to do but you're not sure of the actual name for it.

A lot of time developers will say "yeah, there are unit tests backing it up" when in fact those unit tests are actually  repeatable "integration tests". This is completely fine if this is what they meant to do. But unfortunately they don't know what they are saying and they're probably confusing themselves as well as their peers. I'm hoping this series will act as a reference for some people who are just getting started in testing from a developer standpoint. By no means is this a be-all/end-all series for testing, it is just meant as a springboard for developers just getting started. Testing (TDD in general) has a huge learning curve as it requires quite the paradigm shift to get started. This series will be refined as I find ways to improve it and it will be updated throughout time.

The end goal of this series is to help newcomers to the testing world identify the differences between the types of testing. I will also provide some examples of how to test in each of these scenarios (some more than others) and I will provide information about some of the tools that you can use in each of the testing categories.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

TDD | Testing

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen

About the author

Donn Felker

Senior Consultant
MCTS
ScrumMaster
Agile Practitioner

About Me | Books I Recommend

Gotta Pay The Bills


Tag cloud

    Popular Posts

    RecentComments

    Comment RSS

    Calendar

    <<  September 2008  >>
    MoTuWeThFrSaSu
    25262728293031
    1234567
    891011121314
    15161718192021
    22232425262728
    293012345

    View posts in large calendar