Introduction
The fun part of being able to code is well… the coding right? (provided it works) but in reality so many developers spend a minority of their working day writing code, there are meetings, phone calls, lunch (ok that bit isn’t bad), and all the work around building, testing and deploying software.
An approach to automate build, test and deploy exists in part of the DevOps framework. This topic is very large and cannot be covered in a single post, but this post will demonstrate how you can automate various aspects of this process and hopefully show the significant benefits of doing so.
Making an App
I am going to make a cloud based service hosted on an Azure Function. I LOVE azure functions, you can spin up a service in the cloud and deploy code to them easily and you can use this to create numerous microservices that can be called from any number of apps and services running in your organisation, it allows for a huge amount of flexibility as anything can use these functions provided they can make a http request (which these days is literally anything).
What will our App do
Lets write some simple code that will return a motivational quote upon request, this could be used perhaps by company apps and intranet sites to “motivate employees”.
Firstly we create a new Azure functions product in our IDE. We can see something similar to below
we have an endpoint named “GetQuote” and it will call an asynchronous task which we will code.
We can keep this very simple – we will need a list of quotes and a function to pick one randomly.
Firstly lets make an class that will contain the quote, it will be a simple class with 2 properties;
This class exists simple to hold the data we will be sending to the client from the service.
Next we need a way of picking a quote
The class QuoteBank will handle this, one method is public and it fetches a randomly selected quote from the list it generates.
Easy, development is pretty much done – lets write a unit test.
Writing Unit Tests
Ok, lets write a unit test. But how? we have a randomly selected value from the quotes – we don’t know how to determine which quote would be returned in a test?
correct, I wrote a post previously about this and explained how you can solve this issue in unit testing using mocking frameworks (Fake it Till you Make it – Cookie Codes), however this is a little more complicated. What exactly are we going to mock here? our function doesn’t receive a quote bank object and it never will (why would someone pass it a list of quotes in order to select one?!)
In order to do this we do need to change the way this function works, we will need to use dependency injection to make this function able to be unit tested.
What is Dependency Injection?
Dependency injection is a coding pattern which seeks to invert control of dependencies within code.
The code we have written has a dependency on the Quotebank – we need this to be able to pick a quote. We can’t mock it as our function wont receive it as input, so we need a way of moving this outside the function and injecting it in at runtime.
Why is this Useful?
Dependency injection is useful for 2 key reasons.
- Unit testing – it allows dependencies to be injected into the code at runtime, this means mocking dependencies is possible to isolate tests or gain deterministic results.
- Flexibility of implementations – If tomorrow we decided in this function we wanted to have a database containing quotes instead of a list we could simply inject the QuoteBank at runtime with none of the functions code needing to change, it doesn’t care where the quotes come from just that they are there and they have a method to select a quote
Implmenting Dependency Injection
In azure functions you can create a special class named Startup
This inherits from an interface FunctionsStartup and has to have a void method named Configure. This is where our injection will take place.
Here we are injecting our QuoteBank class and saying it has the interface IQuoteBank.
This interface means that the class we are injecting HAS to contain the method GetQuote as shown below.
So the application at startup knows a class will be injected into it that has a getQuote method returning a quote. It doesn’t care how this works – it could be from a file, a database, a list, whatever. It just cares it exists at startup.
We just need to make a small change to the function itself.
Where we now have an interface of quote bank named _repository this is where the injection reaches our function, and this _repository interface is referenced when obtaining the Quote.
Now we have an interface which is injected on application start up – we can mock its behaviour. In a separate unit test project we can now write a test as below.
We mock the IQuoteBank interface and make it return a quote with author “Test” and quote “Test”. and assert that these are the values obtained in the reply.
Upon running the test we see it passes.
Dev Ops – Automating Build and Test
At this point we have software that builds and passes tests locally. But we want to be able to deploy this automatically to our cloud host. In order to do this we will need a pipeline for building and running this tests.
I will host this code in a Azure Dev Ops Repository and from here I can can create a build pipeline in YAML that will perform the build and run the test.
Initially I need some variables representing my subscription, working directory and the type of vm image used.
These will be referenced in the tasks to follow.
The tasks I want this pipeline to undertake are;
- Build the App (from the latest checkout in the repo)
- Move the relevant files
- Run unit testing
each one of these is a task in the YAML pipeline.
Build;
Archive Files;
Run Unit Testing;
Once these are in place and I save the pipeline I can execute it and see it run the build/test in the cloud.
I can also make it run this automatically on any Pull request to the dev or master branch – so I don’t need to worry about it executing.
What’s really nice about this is if the build or any test cases fail I would see this failure in the dev ops window and be additionally I can be emailed for every build success/failure. Below is a window showing the pipeline has completed;
you can drill into each section and see the logs of each, for example in the unit testing we can drill into a test report.
Showing our 1 test passed.
At this point – we have an automated build and test pipeline, we simply need to merge our development changes into the repository and this will run. But why stop here? lets look at automating the deployment of this app as well
Dev Ops – Automating Releases
Lets say we have various environments in our organisation – Development, Test and Production and these are represented by different resource groups in our cloud provider;
So we would want a pipeline that upon a merge request updated the dev and test environment, but perhaps we wouldn’t want to push to production unless we were happy.
In Azure dev ops you can write specific release pipelines that target resources in your cloud estate with built artifacts.
Here is my pipeline. It has 3 stages to take the build artefact (from the build/test pipeline) and moves it to dev and test – noticed the deploy to prod phase has a man with a tick on it.
Configuring these pipelines isn’t difficult you target the azure function host you want (each separate resource group has one in it) and this would be the endpoint used in dev/test and the one exposed to customers in prod.
Upon running this pipeline I see the following.
the deployment to dev and test has completed – but my approval is required before we start the deployment to production. This is one of the options you can put in place.
you might be asking why have Two pipelines? the reason for this is actually quite simple. We want to do the build once and release those binaries to our required locations rather than having separate builds (which would be a lot harder to monitor what version of software is in each environment).
So our deployment to dev and test is done? lets check if the service works, I can send a requests from PostMan as shown below;
Dev;
Test;
You can see our release pipelines were successful, and quotes can be obtained from these endpoints.
Taking this Approach Even Further
You can take this approach a lot further than demonstrated here, below are some options that would probably be done in the real world
Expanding the Pipelines
You can add a lot more features into the pipeline. I would consider this falls into two groups
- Further Testing – integration tests, load testing etc..
- Deploying the infrastructure – its possible you might want to redeploy your app services upon running the pipeline to guarantee they have the agree on settings, ARM templates representing infrastructure as code could achieve this
- Scale infrastructure – for some releases (ones involving large data updates such as data warehouses) you might want to scale up resources during the deployment. For example you might have a release that updates datawarehouses overnight and to process the data you might want your databases to be more powerful than they need be during the day. Cloud based resources can scale on demand and a step in the pipeline could be written to scale up / scale down at relevant points.
Continuous Integration
One way of expanding this idea is to chain the two pipelines together with no approvals, while this might sound risky if your testing is comprehensive enough you could implement a continuous integration approach. When a merge is made to the master branch the pipelines could run automatically building and deploying directly to production if all the conditions (build success, test completion) are met. This allows for application developments to be released multiple times per day and allows for new feature and bug fixes to be provided to customers much faster than traditional build and deploy approaches.
Conclusions
This is the tip of the iceberg of dev ops, there is literally infinite possibilities and I’ve not touched upon the “business” features of logging stories, monitoring progress, project management etc.. as I start initially it is impossible to cover this topic in one post, but I like to think I have demonstrated how you can automate builds, test and deployments. Why does this matter? well ultimately imagine you have a bug fix or a new feature that is developed. I have worked at organisations where this code would need to be manually deployed into a testing environment, it then takes weeks to test, even if there are no issues. It then is manually moved to a staging environment or targeted for a release in several weeks time. Meaning that from a developer making a change to a change going live can take over a month, Using dev ops and automation you could release these changes minutes after developing them.
The risk adverse will be thinking – well is this a good thing, isn’t it risky? the answer is absolutely Yes this is a good thing, but the organisation needs to invest considerable time upfront in automating testing (unit tests, integration tests, load tests), checking test coverage and having very robust pipelines for build and deployment, this is no small investment in time and effort but the payback if the product is developed on is enormous, not only in the fact once the pipeline exists you save significantly on staff from all the manual tasks, but more importantly you are able to react to changes in a rapid fashion – bet that bug fixes on enhancements your customers want.