Numerous articles on TDD in general and Unit tests in particular, as a rule, operate with rather artificial examples. For example, let's write a function that adds two numbers and write a test for it. Frankly speaking, it is difficult to evaluate the advantages of using automated tests on such examples.
Some time ago, I turned up a tiny proektik who seemed to be specially created in order to test on it a design methodology based on tests. The result of the application struck me! I invite under kat all who still doubt that it is necessary to apply automated tests in daily development.
Lyrical digression. On the role of tests
Many articles have been written about the usefulness of using Unit tests, but many copies are broken in disputes about their necessity. And, despite the fact that I have been practicing the writing and regular running of Unit-tests for a long time, for a long time I was unable to clearly formulate the motives for their use.
The main argument against ambient automated testing is the additional time spent writing tests. Indeed, automated testing does not replace other types of testing. The approach is widespread: “We write the program and send it to testers (or even to the customer immediately!).
If When errors are found, we will correct them and send the corrected version, and so on. ” And this approach can be justified in the situation of “made and forgotten” - when the created program will never have to be finished-redone.
')
Modern flexible development methods offer a completely different approach to creating software. Making changes to the program is considered not as a kind of hypothetical and unlikely possibility, but a completely ordinary part of the work. This encourages developers to create code that is easier to change. To simplify code changes, it is recommended to make the application architecture loosely coupled. And automated tests are designed to reduce the hidden effects of changes. They allow you to detect the deviation of the program from the expected behavior at the earliest stage of making changes.
Some time ago I realized a very simple truth that put everything in its place. No need to consider tests as a kind of addition to the code. Tests are the same part of the code as its architecture. Tests are the part of the code that makes it adapted to make changes with minimal consequences. The better the architecture is, the easier it is to create tests. The better the tests are organized, the fewer the hidden consequences will be after making the changes. And the more reliable and better will be the resulting code.
Every time a question arises to write or not to write a test - repeat the mantra to yourself:
"The test is not an addition to the code, but an important part of it .
"Formulation of the problem
So, the task has been set: to organize automatic transfer of files between two computers over a network.
Specification: Files that appear in a folder on one computer must in some magical way be transferred to a folder on another computer.
- Yes, it's DropBox! - you say.
Yes, cloud storage could be used for these purposes, if not for a few but:
1. Files are slightly confidential, the client does not want to shy to upload them to the cloud
2. File transfer must be logged in detail, so that later the receiving party could not say “was there a boy a file?”
As magic, it is supposed to use a service that tracks changes in a folder on a disk and sends all new files to a web service that is running on the recipient computer. Every successful and unsuccessful transfer attempt must be logged. Moreover, the logging is carried out both by the sender and by the recipient.
As additional requirements, there is the use of WCF and IIS, but this is not so interesting.
We design architecture
Solve the forehead
At first glance, the program architecture is extremely simple. We start the cycle of waiting for a file in the incoming folder, and each new file is read and sent to the service on another computer, which receives data and writes to a certain folder.

The components of the record in the protocol can be reused - which is very good. Everything is simple and clear, we take the keyboard, quickly create a project ...
Stop stop stop. What about TDD? Well, let's think about how we will test such a program. And, in fact, the only way is to write, run and see what happens. And clean up errors as they are detected. Those. A quick and easy way to write a program leads us to the “classic” way of development.
And now let's not hurry
and look at the task a little more carefully.
If you look a little closer, you can see that, despite the apparent difference between the services, they have a lot in common. Indeed, each of them solves the problem of transferring a file from the source to the receiver, simultaneously recording the progress of the transfer. Only source / receiver types are changed.

Moreover, classes that implement the interfaces of the source, receiver and transmitter are very simple and easily amenable to automated testing. And from these classes it is easy, as from a constructor, to send and receive services.


We write tests and the program
TDD classics require you to write tests first - then classes. Honestly, I don’t always succeed - apparently, you need to continue meditating. :-) However, it was surprisingly easy to write working and testing code at the same time. Most often, I first wrote a draft of the working code, then tests for the corresponding class or method, and then brought the code to a working state.
In the process of writing tests, I discovered the beauty of using mock-objects (on the advice of
dorofeevilya, I used
Rhino Mocks ) to test the behavior of classes that depend on abstract interfaces. Here, for example, a test that verifies that in case of an error, the transmitter will write a corresponding message to the protocol.
[TestMethod] void test_that_failed_transfer_logs() {
Run the program
So, all classes are written, all tests are successfully passed. I do the first run without using a web service, in order not to introduce additional difficulties - you first need to check the overall performance. I can easily do this by replacing the receiver-service with the receiver-folder. It turns out a program that transfers files from one folder to another. The result is impressive - the program has earned on the first launch!
Complicate the task - connect the web service. It takes a couple of hours to deploy IIS, configure and configure the service. Another 25 minutes to write and connect a class that implements the receiver interface for the web service. And, as soon as all infrastructure problems were solved - the program earned! Those. I had no problems with the unexpected behavior of the written classes in real conditions.
And draw conclusions
Frankly, the result impressed me greatly. Yes, development took more time than I usually take to develop applications of this level of complexity. But the debugging stage, which usually takes a considerable amount of time, was practically absent! Increasing development time, I, first of all, associate with my lack of experience in writing tests using mock-s. Now I am doing the next project, using the same principles, and writing tests no longer takes me so much time.
It is safe to say that at the initial stage, the introduction of TDD will require some rebuilding of the worldview and is guaranteed to lead to an increase in development time. This should be considered simply as an investment in the development of a new technology. Programs created using the principles of TDD, are not only better, but also much better suited to make changes and additions. And when writing tests becomes a habit, it doesn’t take as much time as it did at first, but automatically.