Introduction
So, you're feeling good at work. Everything is going steady, the projects are moving forward and in general, everyone is happy with you. But...that pesky "johnny on the spot" consultant has arrived and is the new flavor of the month. He's been bringing up all this "test driven" stuff. Being the new gun in the shop, all eyes are mesmerized on him during morning scrum as he touts the awesomeness of test driven development. Man...this guy sounds smart. Well, you've always been interested in test driven development and have good intentions, you're even considered a good developer, and you are a good developer (after all you're reading right now...that's good). However, you've just never worked in a TDD environment in real life scenarios. The way you see it, you have about 24 hours to get up to speed (under the radar of course) before you start to look like you don't know what you're doing. Well, let's get busy...
The Basics
By definition, test driven development (TDD) is an approach taken towards development in which tests are written for the methods that make up a system's modules. By modules I mean classes, the classes that make up a system. Tests are written in code that is part of a test assembly. What's a test assembly? Well, it's just a regular project with regular classes living in their own namespaces. The structure of these classes will make up the organizational units of your tests. The test project will of course require references for the assemblies being tested and class and method level attribute signatures. At the end of the day, it's code that makes up the tests.
Let's set some ground rules and talk about what tests are not.
- Tests are not actual production code: Tests do not make up the business logic or application code a system uses. Also, tests should not live in the same assembly as production code.
- Tests are not a specific language: Tests and the code that make them up are written in regular C# (I'll be using C# in the examples) or any other .NET language.
- Unit tests are not written after a module is completed: Doing so, as you'll soon find out, misses the entire point of test driven development. Integration tests on the other hand can and most likely will be written after the module's code. We'll go over the differences between a unit test and integration test in the next section.
- Tests are not a silver bullet against bad code: Bad code is bad code, a test can't prevent that.
- Tests (TDD) are not a “trend” technology: TDD is not here today and gone tomorrow. TDD is an important part of any project and serves as a valuable member of the team and will be a cornerstone of the project.
Why should we test our code anyway? We've all told ourselves at different times, "I'll see the code work during development and the customers will of course let me know right away if something is broken...". Why should you take the extra steps necessary for test driven development? We've already discovered what tests are not; let talks about what tests are.
- Tests will decouple the application: Finding and compensating for dependencies is a huge part of TDD. A properly written test will cause a system's modules to be decoupled via abstractions. An architecture using abstractions as a focus point will more easily support scalability, extensibility and maintainability. Read my article on the Open/Closed Principle for further insight into this topic. As tests are written, a natural progression of decoupling will take place throughout the system.
- Tests will provide technical documentation: Tests don't lie and the behavior and business logic being tested will provide insight into a system's architecture and code base. In other words, tests provide technical documentation (this is especially true in an agile development environment). One would only need to study the tests to get insight on how the system works. A thorough test (passing of course) will always be an up to date technical representation of the module being tested.
- Tests will maintain the system’s integrity: If a module or piece of code is changed a test will start failing until it's updated. In a continuous integration environment, the team will be immediately notified and the problem can be dealt with in a timely manner. Of course the test should be updated first before changes to the code base are made. This article has not mentioned the "test first code second" mantra yet but here it is; test first code second.
- Tests will help maintain a stable environment: Read the above 3 points again.
Unit vs. Integration
Regarding tests, let's answer an important question. What is the difference between a "unit" test and an "integration" test? Confusion and/or lack of understanding can sometimes revolve around these two terms. A unit test is a test written against a specific module, method or function. There is a catch however. Only behavior and functionality that is in local scope to the item being tested should be engaged during the test. Any objects that are instantiated should not be engaged as they are not in local scope and have reach into other parts of the system. Let's take GetCustomers as an example method. Say for instance GetCustomers instantiates a data access class called Customers_DAL in order to communicate with the database. Should a unit test actually instantiate and execute Customers_DAL? No, unit testing GetCustomers should not instantiate and engage Customers_DAL. The tests' focus is on testing GetCustomers; its focus should not be testing Customers_DAL. In unit testing this concept is accomplished by "mocking". We'll get to mocks in part 2 of this series. Simply put, a unit test should never execute or instantiate code that is outside of the local scope of the method being tested.
Figure 1: Unit tests keep focus local to method being tested. Dependencies are not tested but rather mocked

Integration tests in turn are just what the name describes. All the points of a system are tested and all connection points are integrated. In other words, all dependencies a method being tested has are instantiated and run. If GetCustomers depends on and uses Customers_DAL then an integration test will surface this behavior. The Customers_DAL dependency is not mocked and in turn a real connection to a real database will be made and real data will acted upon. Integration tests are very important and play an important role; after all, the system needs end to end testing or that expensive bug tracking system will never get put to use.
Figure 2: Integration tests execute all dependencies

Regarding scheduling and authoring, there are some important distinctions between unit and integration tests. Unit tests are going to more easily support an automated schedule by allowing a test to focus on the method being tested while mocking up dependencies. Also, unit tests will almost always be written by the developers working on and familiar with the system. Integration tests on the other hand are designed to test the entire chain of dependencies with databases, services and the like being executed. These types of interactions are not necessarily ideal candidates to be run on the same automated schedule as unit tests. When it comes to authoring integration tests, usually a quality assurance department and/or stake holder will use their own tools and processes to write the tests leaving the developer out of the process (we get the bug reports...fun).
As you wade through the world of tests, remember the differences between unit tests and integration tests. Distinct implications exist with each and a good understanding is a good tool to have.
NoteIt's a good practice to keep unit tests and integration tests in different assemblies. Unit tests will more than likely run on a frequently triggered and/or automated basis. However, integration tests, due to their nature may not be ideal candidates to run on the same schedule as unit tests. Either way, having these distinct test types in different assemblies will offer more control on when and how the tests are run.
Behavior vs. Logic
So, what should you be testing? In general, tests can be broken down into two main categories, behavior and logic tests. Behavior tests verify the module being tested "acts" and "behaves" a certain way. Let's go back to GetCustomers. From a behavioral point of view, GetCustomers is expected to instantiate Customer_DAL and make a call. The module's "behavior" is verified by running a test against GetCustomers and asserting the data access call is made. If another developer unbenounst to the original author changed GetCustomers the "behavior" is changed and the test will fail. As we've talked about earlier this action will notify the team immediatly and the issue can be resolved in a timly fashion. Logic tests on the other hand will test an actual algorhytim or business rule. A logical result of an operation will be verified. An example could be a validation method that runs some type of rules validation against a type. The state of the type or the validation results will indicate if the validation method is working as designed. A test author can run different variations against the validation module with expected results.
Putting It All Together and the Big Picture
Well, we've gone through a "lite" introduction on TDD. We talked about what tests are and are not. The benefits of a good testing strategy should be obvious at this point. Key definitions have been identified mainly in the unit vs. integration realm. Finally, the types of tests that can be run have been scaled down to two main categories, behavior and logic. Of course every project has its unique testing strategy and some points mentioned here might not apply or some points might be missing all together. The point of this series is a brief introduction or as I mention in the title, "a quick study". In part 2 of this series I'll go into actual code and introduce you to the world of mocking and "AAA". What you'll start to see and certainly by the end of this series is the big picture that testing is part of. TDD opens the door to a new world of enterprise level coding techniques and architectural design. By being an avid tester, you mind frame and approach to solutions will most definitely change for the better...I see interfaces in your near future!
About Chris Zavaleta
 |
Chris is a software engineer / consultant / architect specializing in Microsoft technologies...and whatever else he needs to learn in the next 10 minutes...
This author has published 2 articles on DotNetSlackers. View other articles or the complete profile here.
|
You might also be interested in the following related blog posts
MSDN Guidance on ASP.NET MVC vs WebForms and its Impact on my EF + ASP.NET Work
read more
Podcast #1 – interview with Ben Scheirman co-author of ASP.NET MVC in Action
read more
TestRail test management beta now available
read more
Using VSTS to Quickly Test Scenarios with Add-In Applications
read more
Dovetail is Hiring a Junior-to-Mid-level .NET Developer
read more
Telerik Introduces Free Web Testing Framework for ASP.NET AJAX and Silverlight
read more
JUST RELEASED: p&p Acceptance Test Engineering Guide, Volume I - beta2
read more
Load Testing vs. Profiling
read more
Code Leader book whats the missing piece?
read more
Testability vs. Testing
read more
|
|
Please login to rate or to leave a comment.