For my current project, some design decisions had to be made to ensure good testability and test coverage.
The basic architecture
Fix is mainly a web server that stores information in files. This means there are two main points where I/O happens, i.e. where information flows in and out of the program. This is a very common pattern that can be seen in many applications: In a tiered architecture, we often see a UI layer at the top and a persistence layer at the bottom. In the case of Fix, instead of the UI I have the REST API.
I/O is hard to test in unit tests. In fact, unit tests should not involve I/O layers at all, because they need to be fast and I/O tends to be slow, which results in a bad testability.
I use TDD, so in principle I want to have all application logic under unit tests. Together that means that the two I/O layers should be very thin and have as little logic as possible.
The web server
I do not want to implement a web server from scratch. It would definitely be an interesting project, if I wanted to learn about sockets and the involved protocols etc. But that is not the topic of this project; I want to focus on the core functionality of Fix.
Therefore I decided to use a third party library that provides the web server functionality. Often these frameworks allow to set up a HTTP server with a few lines of code and all you have to do is handle the requests.
This approach fits nicely to the decision of having thin I/O layers. The actual request processing part can be developed with TDD, the server layer that uses the third party framework can be small and relatively dumb. The only logic happening there happens inside the framework and has been tested by the library developers – assuming it is a well tested library.
What remains to define is the interface between the server layer and the application logic that handles the requests. I don’t want to bind me to a specific server library, so the interface may not use the library’s own request and response structures. Breaking those structures into basic types has to be done in the server layer as well.
The persistence layer
For the other end of the application similar considerations have to be made. Ideally, there should be a layer consisting only of basic file system operations, like creating directories, counting files etc. This can then be mocked away for unit tests that deal with the file based storage logic.
Ideally this layer would simply be a interface for the filesystem part of the standard library. Since that is only coming with C++17, I’ll have to implement it with other libraries that are not yet in the standard, e.g. Boost.Filesystem or Poco.
REST API and storage layer
Between the two I/O layers is the application logic. Since this part is independent of the libraries used for the I/O layers, it can be split into smaller parts as needed. Currently I have identified a rough division into two parts.
One layer deals with the raw calls to the REST API and contains the application logic. The other layer abstracts the storage logic. It is more than mere calls to the file system:
Since the server library might be multithreaded, the storage layer has to take care of the synchronization of the file access. In addition, there can be smaller pieces of logic in the storage layer, e.g. assigning ascending IDs for newly created and stored issues.
In addition, by separating the storage mechanism in a different layer it should in theory be possible to exchange the storage mechanism, e.g. by using a data base.
Conclusion: Testability through separation
I have described earlier that I want to use TDD and BDD for the development of Fix. By separating the I/O parts into their own layers, I can develop everything between them using TDD. The I/O layers themselves can not be unit tested, but they are involved in the acceptance tests which test the whole system
You can find the current state of Fix on GitHub. At the time of this writing, I have not yet separated the file I/O layer from the storage layer. In fact, the whole project is in a rather crude state.