It’s been a while since I have written a post about my hobby project. I am currently working on a big restructuring of the code. The reason why I am doing this is quite straightforward: I want the application to be easier to test, and clearly structured. Apart from this I would like the quality of the code to be good, and to have an idea about the vulnerabilities of my dependencies.
During my time as a developer, I have learned a fair share of things. I would like to take the experience I have gained, and mash everything together in a single project to see if these best practices will guide me towards something I can be proud of. And maybe even something that may be used as an example or case study for others.
I am a great fan of automating (somewhat) technical processes. Mostly because I feel as being a trained monkey if I have to go through the same sequential process regularly. I knew of docker and Jenkins, but have only used a very shallow set of their capabilities until recently. I set up an automated jenkins pipeline for TRAMBU, using docker containers to contain the application. This allows me to automatically build, test, deploy, and post-process my code. I also have the option to automatically release my code, and make every succeeding version publicly available. For TRAMBU, I have yet to decide whether or not I want to fill my github releases page with a whole bunch of intermediate releases, since I like to commit early and often.
Currently, my pipeline looks like this:
It will scan the sources from github at a regular interval (15 minutes), build them, run the unit tests, and then run the module integration tests. If these have all passed, the pipeline will create a docker image of the application and spin it up. At this point, my application will be running on my local machine inside a container. I would like to know if the application has actually started correctly. For this purpose I have a simple smoke test that checks whether or not the main page returns a HTTP status code 200, indicating that all is well.
Next up are a few automated review steps. The pipeline will trigger a SonarCloud analysis of the project, which will tell me if my code is shitty or not. Afterwards, an OWASP dependency check runs on all modules. This check tells me of all the dependencies TRAMBU uses, what licences these are released under, and if my application is vulnerable to any known exploits due to these dependencies. As you can see, the first execution of this dependency check takes a long time. This is due to the fact that it has to pull in a database of known vulnerabilities for the dependencies I use. Usually, this take about fifteen minutes.
I am quite pleased with what I have so far. However, things could always be better. Currently, I would like to plug in one of my other sandbox projects into the pipeline: Aunt Zulma.
In the past, I have messed around with Aunt Zulma. This is an automated acceptance testing tool, based on BDD practices{:target="_blank"}.
This means that a non-technical team member first writes out a scenario in a specified format (GIVEN-WHEN-THEN). Afterwards this
plain text file is added to the testing framework, and a developer writes code that maps these instructions to system calls.
Then the tests are executed, combined the scenario and the technical mapping.
In a team that works with user stories{:target="_blank"} this would allow for
automated acceptance testing. The development flow would look something like this:
This sounds like a pretty major technical challenge. However, it is actually not that hard to set up a system like this. There are great frameworks out there, such as Cucumber BDD{:target="_blank"} and Selenium{:target="_blank"}. My own acceptance testing implementation (Aunt Zulma) is based on the Serenity framework{:target="_blank"}. You can see an older version of this framework in action in a video I have made in the past.
Currently, the application is designed as one big monolith. Internally, the code is divided into separate modules. Furthermore, I have chosen to use an in-memory database that is started an initialized when the application launches. Persistence between different usages of the application is done by regularly writing structured data into a text file. The application is accessed by the user (or any external system) using a web browser. This is not a bad set-up per se, but it does give me some headaches when trying to set up automated tests. The only way an automated test-suite is able to interact with the system now is if it runs an application-controled web browser and clicks on the right items.
The reason why this gives me a headache is dual. First of all web driver tests tend to be slow, and costly. Secondly, I have no way of verifying if the data is stored correctly if it is not displayed to the user. I could just rely on internal unit and limited integration tests, but I feel like more possibilities are opened up if I were to redesign the system.
As stated before, I would like to be able to have more access to my system from my testing environment.
I still want my new tests to be representative of how the system operates. If I were to expose external endpoints to
my testing software that are not used by the system itself, my tests would have little value and carry the risk of
providing false positives. Imagine my writing an external test for a high-level method that the system currently uses to
write data to files on the host system. Later, I restructure my code to use a different method, but leave the existing one as it was.
My test would keep passing, but the system could be broken.
I believe my best bet is to decouple the different modules of TRAMBU. They would interact in a structured way that is also
accessible by and external user or application. My first thought is to introduce a REST layer between the front- and back-end.
This way these module endpoints are accessible by my testing software, and I would no longer need to test functional stories using the
user interface.
Doing the same for my infrastructure layer (mostly database and file I/O) would ive similar results. In this new deployment design, I would still have a need for webdriver tests. They would however no longer by my only means to test functional behaviour in the system. This way the webdriver tests would verify my pages are all there and my theme switching works, while my system tests can focus on making sure all functional rules are followed in my application domain.