October 29, 2010
Continuous Deployment & Evolving The User ExperiencePicking up from a conversation on Twitter between @kentbeck and @BeingAgile about the implications of continuous deployment for user experience, I thought it might be a good idea to put my own thoughts out there.
When we evolve a user interface, what we're really doing is evolving a published API. A user interface is a language that a user has to learn, just like an API. And just like an API, the language of the UI has rules and there are contracts that have to be satisfied.
For example, in this mock-up of a sign-up form, users must enter a valid and unique user name, a valid password and a valid email address before they click "Submit". That's the pre-condition of the Submit function of this visual API.
If we wrote a system test to drive this form, we might have it enter "jasongorman" for the user name, "squiggly123" for the password and "email@example.com" and then have it click the submit button. The assertion for our test would be that a user account is c reated for jasongorman with that password, and an email is sent to the address given with a link that has to be clicked before the account is activated.
Marketing might ask us to change this submission process to allow new users to opt in/out of receiving updates at that email address. This is not a problem if the pre-condition our users originally learned remains unchanged - i.e., whether or not they opted in or out has no effect. And it must also hold that the user gets what they got before, only with a potential added extra outcome. In making this change, we must expect of the user that they do the same or less, and of the application that it does the same or more.
On the other hand, if we asked users to confirm their password before clicking Submit, then we are asking more of them than in the original UI design. We have broken the original contract. We can check if this is the case by re-running our UI test against the updated design. The test would fail because it doesn't confirm the password.
When we do continuous deployment, the UI is potentially evolving continuously, too. This can be a good thing provided each new generation of UI doesn't break the contracts of the previous design. In other words, the user's existing mental model of how the UI works (which is a bit like a set of internal UI test scripts, if you think about it) must not be invalidated.
So we can add new features, providing all the existing features remain. And we can change existing features providing we expect no more of the user (preconditions) and we give them no less than they were getting before (postconditions).
The technical implications are similar to applying the Liskov Substitution principle. We need to know that our extended UI design can be substituted successfully for the old one without breaking any of the old rules. To do this, we need to run the old tests against the new UI. In which case, we need to leave the old tests intact if at all possible. We can achieve this in test automation languages that support inheritence (or an equivalent mechanism) by choosing to observe the Open-Closed design principle on released test modules/classes/scripts - namely that tests should be open to extension and closed to modification. So we might have a SignUpTest test class, which we then extend to create a SignUpWithOptIn test class.
Alternatively, if OCP is not possible in the testing language we're using, we need to make sure we can run a previous version of the tests from our source repository against the latest version of the application.
So when we're continuously deploying our application, our build and deploy process needs to remember to run two generations of UI tests before it proceeds.
But what about when we're not doing continuous deployment and the UI isn't constantly evolving? Need we bother with all this malarky then? Well, I would argue yes, in fact doubly so. Because the less frequent our deployments are, the more changes there are in each release and the wider the potential distance between the user's mental model and the latest UI design.
In my visual API metaphor for the UI, the user is an external system (albeit one with lungs and "needs"). If we were deploying a web service, say, and doing it infrequently with many changes in each release, how much more important would it be to ensure the new API satisfies the old contracts?
Posted 8 years, 9 months ago on October 29, 2010