There are a few good reasons why unit tests are important in a development life-cycle. A unit test is essentially a written contract of how a piece of code must function.
Test driven development (TDD) builds on this foundation to incrementally build solutions that meet the specification.
The theoretical discussion of unit tests is quite an interesting read. Although in this post I describe a practical approach to easily implement unit testing in your projects.
In a future post I will discuss integration testing where the entire system is tested as a whole as a final step.
The video at the end of this post demonstrates how unit tests are developed for a .NET Core app.
In an earlier article I built a sample app to demonstrate synchronous and asynchronous messaging. In this post I will extend the sample to demonstrate the following.
- Building unit tests for each module
- Debugging the unit tests
- Continuous Integration (Ci) of the modules
- Continuous Deployment (Cd) of the app
The Ci/Cd pipelines will ensure the entire project is continuously tested and deployed. Continuous testing is essential in a test-driven environment.
The sample .NET Core app is backend API. However, similar unit-testing is easily replicated with websites and web-socket applications.
The module composition and the unit testing boundaries of the CodeSearch app are shown below.
We will test each module in isolation to assert each part is working correctly. The unit tests should flag bugs, flaws or missing parts of the specification so that corrective measures are taken early.
The modules are also referred to as ‘Nests’ in order explain the relationship with other components of the platform.
The .NET Core app modules are microservices configured with memory and scaling factors as shown above.
The Devkit is used to clone the .NET Core app on the local machine for development and testing. It is customized for each user and obtained using NesterDeploy as shown next.
The Devkit allows developers to quickly and easily setup a full featured development environment locally on the desktop without much effort.
It sets up all the required services, wires up the Git repository and sets up Ci/Cd pipelines for continuous integration and deployment. The Nester Develop, the Visual Studio Code extension is required for the process.
The developer may simply tear down the scaffold without leaving any trace on the system once the task is over. Each team member who has joined the project can access the environment with their own credentials.
Use the Nester Develop Wiki for further instructions on setting things up.
Testing the API Endpoint
The .NET Core app is composed of an API handler that receives requests and workers that do the actual search. I will develop a unit test for the API handler and another for the workers.
The app uses queues for messaging. In order to test a module in isolation we need to mimic the inputs to the module.
This API module takes 2 inputs. It accepts a query term from a user and the results back from Github and Bitbucket modules.
I have setup the unit test to mock the inputs using Moq module. The inputs test-input-a and test-input-b from the search worker modules are read from a file and fed into the controller to test the outcome.
The testing here is only for demonstration. You may choose a more rigorous testing regime in your projects.
Testing the Search Modules
The search worker modules can be tested fairly easily. Input the search expecting a certain result.
Debugging Unit Tests
Invoke the Nest-Unit-Test-Clean-Build command to build the unit test.
Its rather a long name for a command. Although the name was chosen to convey its purpose clearly. It combines cleaning, restoring and building of the unit tests.
Now set the necessary breakpoints and invoke Debug Unit Test to begin debugging. The first invocation attaches to the process. A second mouse click resumes the debugger.
The first breakpoint is hit when debugging starts.
Continuous Integration and Deployment (CiCd)
A Jenkins Ci task pipeline is kick-started each time a module is built. The following sequence of tasks are performed with each iteration:
❶ Start -> ❷ Restore -> ❸ Build -> ❹ Test -> ❺ Report -> ❻ End
The pipeline builds and tests all modules of the app. The unit-tests I developed above will be run and outcomes tested. The report step (5) produces a code-coverage report.
The the Continuous Deployment (Cd) command invokes the following pipeline.
❶ Start -> ❷ Restore -> ❸ Build -> ❹ Test -> ❺ Report -> ❻ Upload -> ❼ Deploy -> ❽ End
The Cd pipeline has 2 additional steps that upload the code and deploy the app on the remote server. The deployment pipeline will include integration testing to confirm the app functions as per the specification. This will be shown in another article.
The reports and pipeline progress can be viewed using the Nest View CiCd command.
Continuous Integration Pipeline
Continuous Deployment Pipeline
The matters discussed in the post is demonstrated in the following video. Integration testing will be discussed in a future article.