On my current project, we’ve built two applications: a shrinkwap virtual appliance that a user installs into their virtualized datacenter, and a phone-home application that the virtual appliance sends data to over an API. If you’re worried that the rest of this post is irrelevant to you because you’ll never build apps like this, stop – the lessons I’ll present should be useful to anyone doing mobile app development with a web server backend. Just think of our virtual appliance as a mobile application, and our phone-home application as the web server backend.
In development, our acceptance suite for our virtual appliance integrates with a running instance of our phone-home application (with interactions recorded via VCR for reliable playback). For a time, the only CI builds we had were the builds for the two separate applications (with any integrations pre-recorded with VCR).
However, once our application launched to production and had real users, we had to consider the following scenarios:
- Does the HEAD of the virtual appliance communicate correctly with the HEAD of the phone-home application?
- Do all of the previous releases of the virtual appliance (that we still support) communicate correctly with the HEAD of the phone-home application?
- If we released the HEAD of the virtual appliance, would it work with the current release of the phone-home application?
- After the virtual appliance pushes all of its data to the phone-home application, do the users of the phone-home application see what we expect them to see?
We first expanded our CI build by one: a build that fires up an instance of our phone-home application on port 3001, then runs the test suite for our virtual appliance without VCR. Actually, we still turned on VCR for all requests other than requests made to our phone-home application (our virtual appliance integrates with lots of other APIs inside a customer’s datacenter that we can’t reasonably setup in a CI environment). However, it’s really simple to tell VCR to ignore only certain requests; for us, we wanted VCR to ignore any requests sent by the virtual appliance to localhost:3001 whenever the “INTEGRATE_WITH_PHONE_HOME” environment variable is present:
VCR.configure do |c| c.ignore_request do |request| request = URI(request.uri) request.host == "localhost" && request.port == 3001 end if ENV['INTEGRATE_WITH_PHONE_HOME'] #..... end
Whenever this CI build runs the virtual appliance’s test suite, it simply has to set the “INTEGRATE_WITH_PHONE_HOME” environment variable before running rake. Without this build, it was too easy for us to change our phone-home application and forget to verify that we didn’t inadvertently break our virtual appliance’s ability to properly send data to it.
We’ve now got an automated answer to #1: “Does the HEAD of the virtual appliance communicate correctly with the HEAD of the phone-home application?” And even better, it was simple to extend this solution to answer questions #2 and #3. It’s simply a matter of checking out different revisions of the phone-home application and the virtual appliance and rerunning this same build to verify compatibility.
Everything I’ve presented up to now should apply to anyone developing a mobile application and an API backend. Question #4, however, is a bit more specific to our particular application. The reasons are complicated, but suffice it so say, it was possible for us to change our phone-home application in such a way that, although we didn’t affect the ability of any version of the virtual appliance to successfully send data to it, we affected the ability of a user to see the results inside the phone-home application. Thus, we had to create an all new build that, after pushing data from a version of the virtual appliance, would then log into the phone-home application and verify that it could, in a very basic sense, see what we expected it to see.
CI builds, particularly ones like these that integrate different applications together, don’t come without a cost. You have to weigh the burden of building and maintaining both the systems and the code that runs the builds against the confidence they give you that you haven’t unwittingly broken anything. We were lucky enough to have a team member that had experience building an application that needed very similar builds and could help us cost the options.