Opening remarks
All my previous posts were about choreography, deployment, topology, and more recently about an attempt to include AI in those systems. This post is a bit apart, because I’m facing a new challenge in my job which is to implement BDD in a CI chain. Therefore, I’m using this blog as a reminder of what I did personally. The following of the Markov saga will come again later.
Introduction
Wikipedia defines the word contract like this:
A contract is a voluntary arrangement between two or more parties that is enforceable at law as a binding legal agreement.
If law usually describes what you can and cannot do, a contract is more likely to describe what’s you are expected to do.
A law’s goal is not only to give rules to follow, but also to maintain a stability in an ecosystem. In IT there are laws, that may be implicit, didactic, empiric, … but the IT with all its laws should not dictate the expected behavior of the customer. But how often have you heard:
“those computer stuffs are not for me, just get the thing done”
“we’ve always done it this way”
There are laws that cannot be changed, but the contract between a customer and its provider could and should evolve.
In IT, like everywhere else where a customer/provider relationship exists, a special need is formalized via specifications. Specifications are hard to follow, but even more they’re hard to evaluate.
The Behavior Driven Development is the assurance that everything have been made respectfully i with the contract ²that has been established between the parties (customers and providers). To do things right, this contract should be established at the very beginning.
Hence, every single item must be developed with all the features of the contract in mind. And then, it should be possible to use automation to perform the tests of behaviour, so that the developer can see if the contract is fulfilled, and if, for example, no regression has been introduced.
In a continuous integration chain, this is an essential piece that can be use to fully automate the process of delivery.
Gherkin
To express the specification in a way that can be both human and computer readable, the easiest way is to use a special dedicated language.
Such a language is known as DSL ( Domain Specific Language).
Gherkin is a DSL that lets you describe software’s behaviour without dealing how that behaviour is implemented
The behaviour is a scenario detailed as a set of features. A feature is a human readable English (or another human language among 37 implemented languages) text file with a bunch of key words in it (eg: Given, And, When, Then,…). Those words do not only help the writer of the feature to organize its idea, but they are used by the Gherkin processor to localize the test of the feature in the code. Of course, there is no magic in it: the test must have been implemented manually.
And here comes Cucumber
The historic Gherkin processor is called Cucumber. It’s a Ruby implementation of the Gherkin DSL. Its purpose is to read a scenario, and to localize the Ruby code that is implementing the all the tests corresponding to the scenario. Finally it executes the code, and for each feature it simply says ok or ko.
Easy.
Nowadays there are many implementation of Gherkin parser for different languages, but in this post I will stick to the Cucumber.
Let’s play
Let’s see how we can implement a basic behaviour driver development with the help of cucumber and Ruby.
The idea here is not to test a Ruby development, but instead to use ruby to validate a shell script.
That’s the main reason why I stick to Ruby (instead of GO which I know better). The Go implementation
(GoDoc, GoConvey, …) relies
on go test
and therefore are related to a pure GO development.
Of course I could do a complete GO development to encapsulate my scripts, but that’s not the point; for my purpose, a scripting
language is a better choice.
Ruby is a scripting language and all the tests implemented here are neither dependent on the Ruby test framework nor on RSpec.
I will write a script that will deploy an EC2 instance via vagrant-aws and install an Openvpn instance on it.
The scenario
The customer point of view
With my role of customer, the feature I’m expecting is:
- Given the execution of the program, and waiting for it to be successful
- Then I may be able to watch netflix US from France.
The feature may be:
|
|
The architect point of view
As an architect the implementation I’m thinking of is
- start an EC2 instance (I will not create it in this post)
- register it to my DNS (with blog-test.owulveryck.info)
- install Openvpn
- configure Openvpn to make it accessible via blog-test.owulveryck.info
The developer point of view
And as a developer, I’m thinking about using vagrant-aws to perform the tasks.
All the implementation will be based on a Vagrant file and a provisioning script.
The vagrant file will be evaluated by vagrant up
on CLI (aka in the real world, by the end user) and
the same vagrant file will be evaluated within my cucumber scripts.
Therefore I can say that I am doing BDD/TDD for a configuration management and provisioning.
The basic feature
I will describe here a single feature, just for testing purpose.
Setting up the Ruby environment
I will use the Ruby implementation of cucumber.
To install it, assuming that we have a gem
installed, just run this command
|
|
This will load all the required dependencies.
It may also be a good idea to use bundle
if we plan to do further development of the steps in ruby.
The test environment with bundler
The whole development will run with the help of bundler (and RVM). See this post for more explanation on how I set it up on my Chromebook.
|
|
the Gemfile
Let’s add the cucumber, vagrant (as installed in a previous post ), and vagrant-aws dependencies in the Gemfile:
|
|
and then install the bundle:
|
|
And now let’s run cucumber within the bundle:
|
|
The skeleton of the test
First, as requested by cucumber, let’s initialize a couple of files in the directory to be “cucumber compliant”. Cucumber do have a helpful init function. Let’s run it now:
|
|
Adding the feature file
In the features/ directory, I create a file basic_feature.feature
which contains the YAML we wrote earlier, then I run cucumber again.
|
|
We notice that the feature has been read and understood correctly by cucumber. ON top of that Cucumber gives the skeleton of a ruby implementation for the tests.
I will copy all the ruby code in its own file:
|
|
And run cucumber once more:
|
|
Cool, the framework is ok. Now let’s actually implement the scenario and the tests
Implementation of the “Given” keywords
There is not much to say about the Given keyword. I can test that I am really on my Chromebook but that does not make any sense. I will skip this test by not implementing anything in the function.
Implementation of the “When” keyword
The actual execution of the “When” is the execution of the Vagrant file. It will start the EC2 instance and provision the VPN I also need to mount the VPN locally afterwards
|
|
(trying to) Implement the netflix test with selenium
To test the access, instead of faking my browser with curl, I will use the selenium tool.
So I add it to my Gemfile and bundle update
it (informations comes from this starterkit):
|
|
Then I need to create a special file in the support
subdirectory to define a bunch of objects:
|
|
I’m also adding the files from the starterkit in the lib
subdirectory.
As I am developing on my Chromebook, I also need the chromedriver
Too bad chromedriver relies on the libX11 that cannot be installed on my Chromebook / end of show for selenium on the Chromebook… for now
Note I will continue with the development, but be aware that I won’t be able to test it until I am on a true linux box with the chromedriver installed
|
|
The actual implementation of the scenario
What I need to do is to implement the scenario. Not the test scenario, the real one; the one that will actually allows me to launch my ec2 instance, configure and start Openvpn.
As I said before, I will use vagrant-aws to do so.
Note vagrant was depending on bsdtar, and I’ve had to install it manually from source:
(tar xzvf libarchive-3.1.2.tar.gz && ... && ./configure --prefix=/usr/local && make install clean
)
Installing vagrant-aws plugin
The vagrant-aws plugin has been installed by the bundler because I’ve indicated it as a dependency in the Gemfile. But, I will have to have it as a requirement in the Vagrantfile because I’m not using the “official vagrant” and that I am running in a bundler environment:
Vagrant’s built-in bundler management mechanism is disabled because Vagrant is running in an external bundler environment. In these cases, plugin management does not work with Vagrant. To install plugins, use your own Gemfile. To load plugins, either put the plugins in the
plugins
group in your Gemfile or manually require them in a Vagrantfile.
Installing the base box
The documentation says that the quickest way to get started is to install the dummy box. That’s what I did:
|
|
The Vagrantfile
The initial Vagrantfile looks like this:
|
|
So all the rest in the basic implementation of the vagrant file and the provisioning.sh for the Openvpn configuration. but that goes far behind the topic of this post which was to introduce myself to BDD and TDD.
Conclusion
I’ve learned a lot about the ruby and cucumber environment in this post. Too bad I couldn’t end with a fully running example because of my Chromebook.
Anyway the expected results were for me to:
- learn about BDD
- learn about cucumber
- learn about Ruby
- learn about vagrant
I can say that I’ve reach my goals anyway. I will try to finish the implementation on a true Linux box locally, or on my Macbook if I have time to do so.