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:
At a high level, here’s how the application works:
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:
- New up an instance of Customer Service
- Create a customer
- Ensure the CustomerRespository Works (who knows what that means, maybe you didn’t write it)
- 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:
Leave a Reply
You must be logged in to post a comment.