Introduction
Oracle’s Developer Cloud Service (DevCS) is a great tool for teams of developers. It provides great tools for continuous delivery, continuous integration, team collaboration, scrum boards, code repositories and so on. When using these feature, you can leverage the best practices in an application lifecycle to deliver high quality and manageable code.
One of the phases of an application lifecycle we are going to focus on today is the testing phase. Tests can take place on the developer’s machine and by leveraging these test in an automated way on DevCS, we ensure the quality of the code throughout the lifecycle of the code.
Main Article
In this article we will take a closer look at using Node.JS in combination with Jasmine to test our code and configure an automated test script that will run every time a developer pushes his code to a specific branch in the code repository.
Why testing?
To many developers it is clear that testing can be advantages however many feel that testing adds an overhead to their already busy schedule. This is mainly a misconception as proper testing will increase the quality of the code. If you don’t test, you will spend more time debugging your code later on so you could say that testing is a way of being lazy by spending some more time in the beginning.
In addition to this, testing is not just a tool to make sure your code works, it can also be used as a design tool. This comes from the Behavior-driven development paradigm. The idea is to define your unit test before writing any code. By doing this, you will have a clear understanding of the requirements of the code and as such, your code will be aligned with the requirements.
This also increases the re-usability of the code because a nice side effect of designing your code this way is that your code will be very modular and loosely coupled.
When we talk about Node.JS and JavaScript in general, the side effect of a “test-first” approach is that it will be much easier to reuse your code no matter if it’s client side JavaScript or server side JavaScript. This will become clear in the example we will build in this article.
Different types of test
When we take about writing tests, it is important to understand that there are different types of tests, each testing a specific area of your application and serving their own purpose:
Unit Tests
Unit tests are your first level of defense. These are the tests run on your core business logic. A good unit test does not need to know the context it is running on and has no outside dependencies.
The purpose of a unit test is like the name says: to test a unit of work. A typical example of a unit test is to test a function that checks if a credit card number is valid. That method doesn’t need to understand where the credit card number is coming from, nor does it need to understand anything around security or encryption. All it does is take a credit card number as input and returns a true or false value depending on the validity of the number.
Integration Tests
The next level of tests are the integration tests. These will test if all your different modules integrate well and test if the data coming from external sources is accurate.
It will group the different modules and check if these work well together. It will check for data integrity when you pass information from one module to another and makes sure that the values passed through are accurate.
End 2 End Tests
An end 2 end test typically requires a tool that allows you to record a user session after which that session is replayed. In a web application, Selenium is popular tool to perform these E2E tests. In such a scenario, you will define certain areas on the page that you know should have specific value. When the HTML of these areas are different from what you define, the test will fail. This is the highest level of testing you can have.
In this post we will focus on unit testing.
Creating a new project on Developer Cloud Service
Before we can start writing code, we need to define a project in Developer Cloud Service (DevCS). A project in DevCS is much more than a code repository. It also allows us to manage the development lifecycle by creating tasks and assigning them to people. It also provides a scrum board so we can manage project in an agile way.
In this post, we will create a microservice that does temperature conversion. It will be able to convert Celsius and Fahrenheit temperatures to each other and Kelvin.
In DevCS we define a new project called “Converter”:
As template we select the “Initial Repository” as this will create the code repository we will be using to check in our code.
In the next step, we define the properties and we initialize a repository with readme file:
Now we can continue and create our project.
Once the project is created, you will see your project dashboard:
As you can see, the system created a repository called converter.git. On the right hand side you can find the HTTP and SSH links to the repo. We will need the HTTP link in order to clone the initial repository before we can start coding.
Once you copied the HTTP link to your GIT repo, you can open a command line so we can clone the repo.
At the location you want the repo to be created, we simply execute following command:
D:\projects\Oracle\testing>git clone https://<yourRepo> Cloning into 'converter'... Password for 'https://yannick.ongena@oracle.com@developer.us2.oraclecloud.com': remote: Counting objects: 3, done remote: Finding sources: 100% (3/3) remote: Getting sizes: 100% (2/2) remote: Compressing objects: 100% (37/37) remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. Checking connectivity... done.
This will clone the repository into a folder called “converter”. At the moment that folder will only contain a README.md file. The next step is to initialize that folder as a node.js project. This can easily be done by using the npm init command:
D:\projects\Oracle\testing>cd converter D:\projects\Oracle\testing\converter>npm init This utility will walk you through creating a package.json file. It only covers the most common items, and tries to guess sensible defaults. See `npm help json` for definitive documentation on these fields and exactly what they do. Use `npm install <pkg> --save` afterwards to install a package and save it as a dependency in the package.json file. Press ^C at any time to quit. name: (converter) version: (1.0.0) description: entry point: (index.js) app.js test command: git repository: (https://<yourURL>) keywords: author: license: (ISC) About to write to D:\projects\Oracle\testing\converter\package.json: { "name": "converter", "version": "1.0.0", "description": "converter.git", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "<yourURL>" }, "author": "", "license": "ISC" } Is this ok? (yes)
This will have created the package.json file.
The next thing we need to do is installing the required modules.
For this example we will use express and the body-parser module to give us the basic middleware to start building the application. For testing purpose we will use jasmine which is a popular framework for behavior driven testing. Jasmine will be configured as a development dependency.
We will add following content to the package.json:
"dependencies": { "body-parser": "^1.15.2", "express": "^4.14.0" }, "devDependencies": { "jasmine": "^2.5.2" }
Now we can simply install these modules by executing the npm install command from within the application’s folder.
Writing tests
Now that the project has been setup and we downloaded the required dependencies, we can start writing our code, or should I say, writing our tests?
Like I said in the introduction, we can use testing as a tool to design our service signature and this is exactly what we are going to do.
Jasmine is a perfect framework for this as it is designed to define behaviors. These behaviors will be translated to units of code that we can easily test.
If we think about our temperature converter that we are going to write, what behaviors would we have?
- Convert Celsius to Fahrenheit
- Convert Celsius to Kelvin
- Convert Fahrenheit to Celsius
- Convert Fahrenheit to Kelvin
Each of these behaviors will have its own piece of implementation that can be mapped to some testing code.
Before we can write our tests, we need to initialize the project for Jasmine. This can be done by executing the jasmine init command from within your application root:
node node_modules/jasmine/bin/jasmine.js init
This command will create a spec folder in which we need to write the specifications of our tests.
In that folder we create a new file converterSpec.js
It is important to end the filename with Spec because Jasmine has been configured to search for files that end with Spec. You can of course, change this behavior by changing the spec_files regex in the jasmine.json file in the support folder but by default Jasmine will look for every file ending in Spec in the spec folder.
The contents of the converterSpec.js will look like this:
describe("Converter ",function(){ it("converts celsius to fahrenheit", function() { expect(converter.celsiusToFahrenheit(0)).toBeCloseTo(32); expect(converter.celsiusToFahrenheit(-10)).toBeCloseTo(14); expect(converter.celsiusToFahrenheit(23)).toBeCloseTo(73.4); expect(converter.celsiusToFahrenheit(100)).toBeCloseTo(212); }); it("converts fahrenheit to celsius", function() { expect(converter.fahrenheitToCelsius(32)).toBeCloseTo(0); expect(converter.fahrenheitToCelsius(14)).toBeCloseTo(-10); expect(converter.fahrenheitToCelsius(73.4)).toBeCloseTo(23); expect(converter.fahrenheitToCelsius(212)).toBeCloseTo(100); }); it("converts celsius to kelvin", function() { expect(converter.celsiusToKelvin(0)).toBeCloseTo(273.15); expect(converter.celsiusToKelvin(-20)).toBeCloseTo(253.15); expect(converter.celsiusToKelvin(23)).toBeCloseTo(296.15); expect(converter.celsiusToKelvin(100)).toBeCloseTo(373.15); }); it("converts fahrenheit to kelvin", function() { expect(converter.fahrenheitToKelvin(32)).toBeCloseTo(273.15); expect(converter.fahrenheitToKelvin(14)).toBeCloseTo(263.15); expect(converter.fahrenheitToKelvin(73.4)).toBeCloseTo(296.15); expect(converter.fahrenheitToKelvin(212)).toBeCloseTo(373.15); }); });
These tests will fail because we haven’t written a converter yet.
We can execute this test suite by calling Jasmine from our root directory of the application:
node node_modules/jasmine/bin/jasmine.js
The output will contain some erros and a message saying that 4 out of 4 specs have failed:
D:\projects\Oracle\testing\converter>node node_modules/jasmine/bin/jasmine.js Started FFFF Failures: 1) Converter converts celsius to fahrenheit Message: ReferenceError: converter is not defined Stack: ReferenceError: converter is not defined at Object.<anonymous> (D:\projects\Oracle\testing\converter\spec\converterSpec.js:9:16) 2) Converter converts fahrenheit to celsius Message: ReferenceError: converter is not defined Stack: ReferenceError: converter is not defined at Object.<anonymous> (D:\projects\Oracle\testing\converter\spec\converterSpec.js:16:16) 3) Converter converts celsius to kelvin Message: ReferenceError: converter is not defined Stack: ReferenceError: converter is not defined at Object.<anonymous> (D:\projects\Oracle\testing\converter\spec\converterSpec.js:23:16) 4) Converter converts fahrenheit to kelvin Message: ReferenceError: converter is not defined Stack: ReferenceError: converter is not defined at Object.<anonymous> (D:\projects\Oracle\testing\converter\spec\converterSpec.js:30:16) 4 specs, 4 failures Finished in 0.01 seconds
By writing these tests, we established that our convertor should have following methods:
- celsiusToFahrenheit
- fahrenheitToCelsius
- celsiusToKelvin
- fahrenheitToKelvin
Implementing the converter
Once the signature of our code has been established, we can start implementing the code.
In our case, we need to create an object for the converter with the required functions. Therefore we create a new file converter.js with the following content:
var Converter = function(){ var self = this; } Converter.prototype.celsiusToFahrenheit = function(temp){ return temp*9/5+32; }; Converter.prototype.fahrenheitToCelsius = function(temp){ return (temp-32)/1.8; }; Converter.prototype.celsiusToKelvin = function(temp){ return temp +273.15; } Converter.prototype.fahrenheitToKelvin = function(temp){ var cel = this.fahrenheitToCelsius(temp); return this.celsiusToKelvin(cel); } if (typeof exports == 'object' && exports) exports.Converter = Converter;
Now that the implementation is done, we can include this file in our converterSpec.js so the test will use this object:
At the top of converterSpec.js add the following lines:
var Converter = require("../converter").Converter; var converter = new Converter();
If we rerun the jasmine tests we will notice that they succeed:
D:\projects\Oracle\testing\converter>node node_modules/jasmine/bin/jasmine.js Started .... 4 specs, 0 failures Finished in 0.005 seconds
So far, wrote some tests and implemented a plain old JavaScript object. We haven’t written any server specific code but yet, our core business logic is already done and tested.
Notice how we wrote this code without worrying about things like request body, response objects, get, post and other server specific logic. This is a very powerful feature of writing test in this way because now the exact same code can be used in any project that used JavaScript. No matter if it’s Node.JS, Oracle JET, Angular, Ionic,… it should work in any of these frameworks and we didn’t even spend additional time optimizing the code for this. It’s just a bi-product of a test-first approach!
Implementing the server
The last step is to write our server that consumes the converter. Our server will expose a single endpoint where we can specify an object with a temperature value and a units value. Based upon the units value, the converter will make al the required conversions and send the result back to the user.
Create a new file app.js with following contents
var express = require("express"); var parser = require("body-parser"); var app = express(); var http = require('http').Server(app); app.use(parser.json()); var Converter = require("./convertor").Converter; var converter = new Converter(); app.post("/convert",function(req,res){ var temp = req.body.temp; var units = req.body.units; var result = {}; if(units.toLowerCase() == "f"){ result.fahrenheit = temp; result.celsius = converter.fahrenheitToCelsius(temp); result.kelvin = converter.fahrenheitToKelvin(temp); } else if(units.toLowerCase() == "c"){ result.celsius = temp; result.fahrenheit = converter.celsiusToFahrenheit(temp); result.kelvin = converter.celsiusToKelvin(temp); } res.send(result); res.end(); }); http.listen(3000, function(){ console.log('listening on *:3000'); });
Setting up automated testing on Developer Cloud Service
Once we have a first finished version of the code, it’s a good time to commit our code to the code repository. At the same time, we want to setup a build process on DevCS so that every time we commit code to the repository. it will fire of the tests we created so far.
In order to do this, we first need to modify the package.json so that we can make use of the npm test command to start the test.
This is fairly simple as npm test is just a shortcut to a script you define in the package.json. This should be the same command as we use when starting the tests from our command line.
Modify package.json so the scripts part looks like this:
"scripts": { "test": "node node_modules/jasmine/bin/jasmine.js" },
When you save the file and execute npm test from a command line in the root folder of your application, it should start the tests.
Adding a .gitignore file
The next step we have to do before committing the code, is to add a gitignore file. This file will tell GIT what files and folder to ignore. The reason why we want this is because it’s a bad practice to include the node_modules folder in your code repository. The code in that folder isn’t written by us and we can simply initialize a new consumer of the repo by executing npm install. This way the modules don’t take additional space in the repository and it will be much faster to upload the code.
The .gitignore file needs to be put in the root of your application. For this application we only need to ignore the node_modules folder so the file will look like this:
# Dependency directories node_modules
Creating a build configuration in DevCS
Before we commit the code, we need to setup a build configuration in DevCS.
A build configuration is a sequence of actions that can be configured depending on the type of application. For example when you are developing a J2EE application, the build configuration can execute a maven build, build the JAR/EAR file and pass it on to a deployment script so it can be deployed automatically to Java Cloud Services.
In our case, we are working with Node.JS so technically we don’t have anything to build. However, a build config can still be usefull because it allows us to execute certain command to test the integrity of the code. If everything passes, we are able to hand it over to a deployment profile for Application Container Cloud Service to deploy it on the cloud.
In this step, we will focus on the build step.
In DevCS, select your project and go to the Build page. At the moment only a sample maven_build has been created which doesn’t do us any good so we will go ahead and create a new job.
Once we saved the job we will be redirected to the configuration.
The Main and Build Parameters tab can remain unchanged. In the Source Control tab we specify that the build system integrates with a GIT repository.
From the Repository drop down, we select our converter repo.
In the Branch section we click the Add button and select master. This way we can specify on which branch of the code this build applies.
It is a common practice to use something like GitFlow to develop features. Each feature will be represented by a branch and once the feature is finished, that branch is merged into a development branch. In these cases, it makes a lot of sense to only initiate the build when a commit is done towards the development branch so that’s why we specify a certain branch in this step. If we don’t specify a branch, the build will start on every single commit.
In the next tab, Triggers, we specify what triggers the build. Because we re relying on a commit to the source control system, we have to select “Based on SCM polling schedule”. This links the configuration from the Source Control tab to the Trigger.
The next tab Environment isn’t required in this step so we can go ahead and open the Build Steps tab. This is where we configure the actions that are done when the build starts.
In our case we want to execute the npm test command which is a shell script. From the add button we select the Execute Shell step. This will add a text area in which we can specify shell commands to execute. In this box we can add multiple lines of code.
Add the following code in the command box:
git config --global url.https://github.com/.insteadOf git://github.com/ npm install npm test
Because we have added the node_modules to our gitignore file, we need to install the modules from our package.json. On your machine a simple npm install would be sufficient, however because DevCS is behind a firewall that only accepts traffic on port 80 (HTTP) and 443 (HTTPS), we need to make sure that we force git to use HTTP and not the git protocol. The git config command does make sure that we download all the modules over regular HTTPS traffic, even if the git repository of a module has been configured using the git protocol.
After that we can install the modules using the npm install command and once this is done, a npm test will start the Jasmine tests.
Our build config is now complete.
Committing the code
Now that our build config has been setup, we can commit and push our code after which the build should start.
Commit the code using your favorite GIT client or from within your IDE. After the commit, push the changes to the master branch.
Once you have pushed the code, go back to the Jobs Overview page on DevCS and you will notice that our new build config has been queued and after a few seconds or a minute it will start:
After aboud half a minute, the build should complete and you should see the status:
On the right hand side you have a button that can take you to the Console Output. This gives you a good overview of what the build actually did. In our case, everything went fine and it ended in success however when your test fails, the build will fail and the console output will be crucial to identify what test failed.
This is the output from my build (I omitted the npm install output).
Started by an SCM change Building remotely on Builder 22 Checkout:<account>.Converter Unit test / /home/c2c/hudson/workspace/developer85310.Converter Unit test - hudson.remoting.Channel@2564c81d:Builder 22 Using strategy: Default Checkout:<account>.Converter Unit test / /home/c2c/hudson/workspace/developer85310.Converter Unit test - hudson.remoting.LocalChannel@ebc21da Cloning the remote Git repository Cloning repository origin Fetching upstream changes from https://developer.us2.oraclecloud.com/<account>/converter.git Commencing build of Revision af98f72759ebdfc7a88b0a1f49d70b278bdcbab4 (origin/master) Checking out Revision af98f72759ebdfc7a88b0a1f49d70b278bdcbab4 (origin/master) No change to record in branch origin/master [developer85310-chatbotdev1_converter_12171.Converter Unit test] $ /bin/sh -xe /home/builder/tmp/hudson2474779517119792441.sh + git config --global url.https://github.com/.insteadOf git://github.com/ + npm install <npm install output> + npm test > converter@1.0.0 test /home/builder/hudson/workspace/<account>.Converter Unit test > node node_modules/jasmine/bin/jasmine.js Started .... 4 specs, 0 failures Finished in 0.01 seconds Finished: SUCCESS
Conclusion
In this post we have shown how we can leverage the power of Developer Cloud Service to setup an automated test build for your Node.JS code. By doing this, you not only get the benefits of getting instant feedback when your code is pushed to the repository but you also get better quality and re-usability of your code.
All content listed on this page is the property of Oracle Corp. Redistribution not allowed without written permission