Are you tired of days spent in front of the screen, with no results to show? Have you once again engaged in yak shaving? Today, after having failed previously, I have finally managed to solve a problem while avoiding this trap by following rigorously two guidelines preached by grandmaster programmers. Be warned: Following this approach, you will get a working solution – but you won’t like it. It will be ugly, stained by compromises, far from the elegant solution you wish for. But if your resources are limited and you want to avoid death by too many yaks, this is your only option. But first, what are these guidelines?
One: Maintain a laser-sharp focus. A great programmer is constantly aware of what she is trying to achieve and never strays far from it. If the path leads away, she backs up. If something else pops up, she writes it down for later and gets back to the job. This is essentially about deciding what not to do. (Many thanks to Kent Beck for sharing his focus secret!)
Two: Always find the simplest, smallest thing – a stepping stone – that moves you toward the goal. It might not be especially pretty but perfect is the enemy of good. (“Make it work, make it right, make it fast.” – Kent’s father, reportedly.)
In addition, there are a few more crucial ingredients. Awarness of cost/benefit so that you know when to give up because it is not worth the effort. Coming up with various alternatives so that you don’t get locked to a single solution. Doing the most unclear and risky things first rather than the nice, easy ones.
My goal, that I have previously failed to achieve due to too many dead ends, was to introduce tests to our front-end application. This was complicated by the use of bleeding edge technologies (=> you’ll bleed when using them) such as EcmaScript 6 and React/Jest, the need to stub/mock parts of the application (XHR) that were
required by the module under test, and a build relying on Browserify and Gulp.
As my highly respected colleague and master of all things front-end Pål Ruud commented:
Mix of Jest, Browserify and ES6 has turned out to be really difficult to make to play together. :-(
I believe I made six different attempts to get a test to run, with different combinations of test frameworks and 6to5, es6ify, traceur.
One of my weaknesses is that I find it difficult to resist such a challenge. Especially regarding missing testing. So I begun my attempt by listing the goal, alternatives, and top risks:
This lists the goal – which, fully expressed, would read “Create the simplest possible test of a simple method of a (Flux) Store class,” the main questions (such as whether to run the tests under Node.js or the browser), and, in red, the points of highest risk – the necessity to pre-process the ES6 files and to mock (or rather stub) XHR during the tests.
The simplest & smallest guideline implied that I should reuse the build workflow we already had – Gulp with browserify taking care of es6to5, reactifying etc. – and thus run tests in the browser instead on Node.js (which is considerably slower but also less constraining, enabling us to test also UI-related code in the future). Among the technologies discussed with Pål I have thus also opted for the more mature ones (primarily Karma over Testem). It also required that I stayed with the built-in
assert rather than using something fancy like Chai (though switching over could be a future step). So here is the resulting plan:
- Verify we will be able to mock XHR during tests (otherwise we’d need to either find a different solution or change the design to factor out XHR calls) <- proxyquireify
- Create a dummy test and make Browserify include in the output .js file
- Get a minimal Karma+Mocha test running in a browser, not touching our production code yet
- Require the Store from the test and verify we can call a simple method on it
- Stub XHR so that we can test an actual business method though, for the sake of simplicity, we won’t care about data returned by XHR
This led me indeed to a solution though it is very far from what I would like to have. I had to intentionally sacrificy a lot for the sake of getting a working solution [soon]:
- Ability to run tests instantly (i.e. < 3s) and automatically as soon as I save a file – this requirement has nothing to do with the goal itself and is a path to yak hordes
- Running tests under Node instead of a browser that incurs a great overhead due to browserifying, es6to5-fying, and JSX-fying and starting a real browser
- Using the less invasive PhantomJS (it failed for some reason, while Chrome worked)
- Running tests from our watch task or as a part of our build task – for the sake of simplicity, I have created a separate task that has to be invoked separately
- Being able to make XHR calls return data synchronously in a test so that I could fake what back-end would return and verify the outcome (we use Promises that run asynchronously; there are workarounds but well beyond the scope of my goal – and making our stores decoupled from XHR is anyway a better solution)
I am sure we will eventually find a solution for all of these. But the most important thing is that we made a first step in that direction and that we can now create and run tests (however slow it is). I can finally sleep without Kent hunting me in my dreams :-).
To be able to arrive to a solution in a reasonable time, we must be able to focus on our true (and minimalist) goal and take small and simple steps – even though our heart of programmer is bleeding at their lack of elegance and efficiency. Once we have a solution, we can iteratively improve it. In any case, imperfect something always beats a perfect nothing. (Well, unless we talk about Zen.)
Needless to say, I still suck both at maintaining the focus and doing the simple but inelegant.