The first two blog posts in this series covered my introduction to Growing Object Oriented Software Guided by Tests (GOOS), and my first, overly challenging attempt at implementing its worked example in JRuby which resulted in me mothballing that implementation until I'd completed it in Java. This post describes my approach at tackling it in Java; the challenges I faced, how I hope this blog might benefit others, and how I'm now ready to dust off the JRuby code and have another go.
Following the challenges I faced using JRuby, I decided to follow the auction sniper worked example as closely as possible. I was concerned that any deviation would be punished with non-compiling or test breaking code, the only salvation being an uncharted and possibly impossible ramble back to the right path. I thus used Java 7, JUnit, Hamcrest, WindowLicker and the Smack library as used in the book. However, I did deviate in two ways:
I'd already substituted Openfire with the Vines XMPP service in my earlier JRuby implementation. Vines is written in Ruby, and I thought it sensible to only have one runtime dependency for the whole project. This ended up an unachievable goal, but I did persist with Vines deployed using Docker as described below.
I substituted jMock with Mockito due to familiarity with my favoured Java mocking framework. I later discovered that this was a mistake as I believe I failed to gain some insight into writing non-brittle tests using strict mocks by improving object design. Instead, I followed my more familiar path of reducing brittleness with looser mocks. Although I ended up with the same design as found within the book, this was through guidance rather than me listenting to the tests. I would strongly advise future Java implementers to use jMock.
In general, it went pretty well. It took longer to complete than I hoped and thought it would, and while there were no ground breaking revelations, it really did validate and clarify my thoughts and approach to outside in / BDD / ATDD that I've built up over the past 10 years. I hoped that it would provide guidance in developing non-brittle tests; those that would require minimal change when the system under test changed. I recall several conversations over the past couple of years with colleagues and friends regarding whether the brittleness of tests have reduced their net value, and I've often found myself justifying tests in discussions triggered by 'Is TDD Dead?'. Even with my mistake in using Mockito, I do believe the book has advanced my approach in object design and ability to write non brittle tests. In fact, only this week I've implemented immutable value types at work, and my class and method naming is moving towards domain concepts and away from the patterns and implementations of the class - no more BlahService or WatDecorators.
I really enjoyed this book, and working through the auction sniper although there were a few challenges. One frequent concern I suffered was 'am I doing it right?'. The authors didn't hand hold the reader through the exercise intentionally. At times, detailed steps were given, but at other times just general and sometimes ambiguous advise. My interpretation of this advise occasionally led to non compiling code, failing tests, or simply a lack of confidence in my solution. In such times, I would have appreciated comparing my solution with that of the authors, which was unfortunately impossible as their public solutions hosted on Github had squashed commits. I ended up using Evgeni Dzhelyov's solution as a reference when I got stuck.
As well as the long running concern about 'doing it right', I faced a few specific challenges during my completion of the auction sniper. Whilst at times, these provided a little frustration, distraction, and delay in ultimately completing the worked example, they also offered opportunities to learn. I grabbed a couple of these opportunities, and turned down the rest whilst desperately avoiding to shave yaks.
Following Andy Henson's advise, I decided to use the Vines XMPP service for the JRuby and then the Java auction sniper implementations and I decided to trial Docker as a lightweight deployment solution rather than installing Vines natively. Using Docker would minimise the dependencies I would have to install on my development machines, provide greater repeatability, and as an unexpected side benefit ease continuous integration on CircleCI which only took around 10 minutes to set-up including the Docker test prerequisite configuration.
Learning Docker was fairly trivial, especially when using my work laptop running Antergos Linux. I did find it slightly more complicated on my MacBook Air due to the Boot2Docker shim and port forwarding configuration required to allow the Java code to communicate with Vines within the Docker instance. However, the Docker experience I gained through this project has definitely been a net plus when balanced with the time and effort taken.
This was most definitely the most difficult problem I had to solve on my way to completing the auction sniper, and the reason I ended up mothballing the Ruby implementation until I'd successfully completed the Java one.
Steve and Nat's example used Openfire with plain authentication which was appropriate as security is not the focus of the book. The Smack Java XMPP client library was struck by a security vulnerability in April 2014 related to the ServerTrustManger / Java TLS certificate handling. A combination of using the latest version of Smack and the Vines XMPP server meant plain authentication wasn't an option, and I'd have to implement certificate sharing in my implementation.
I spent many hours looking at Smack examples and forum posts, posted on StackOverflow. Nothing appeared to work, and it became evident I'd have to take a slightly deeper dive in Java TLS security than I had the appetite for. A couple of conversations on the Smack IRC channel finally hinted at a solution, and I even helped Flow test a more streamlined certificate approach that may be included in a future release. This particular commit describes the connection configuration I added to the auction sniper, and fake auction server. The readme describes how to download dynamically created certificates from newly provisioned Vines Docker instances, and how to register them in a project keystore.
I suffered a combination of issues that caused significant distraction and frustration as I closed in on completion. I had gone with the authors naming convention for the end to end tests rather than using the conventional IT suffix used by Maven to distinguish unit and integration tests. Early on, this mistake provided a benefit. Simply running 'mvn test' would run all unit and integration tests.
Unfortunately, near the end of the auction sniper, addition of a unit test that covered integration of Swing not only resulted in itself failing, but also caused intermittent and previously unseen failure of the end to end tests that executed later in the 'unit test' run.
The simultaneous appearance of both symptoms led me to the incorrect assumption that they were related. I was wrong. Rather, two unrelated issues triggered by this new unit test occurred at the same time:
A defect in WindowLicker prevented reliable typing of test data into the Swing auction sniper; "Item 123" in the auction text box would be mistyped "tem 12" missing the first and last character and the tests would obviously fail.
The 'unit tests' and 'end to end tests' were executed in the same process. A still undiagnosed issue meant that earlier execution of the unit tests would cause previously robust end to end tests to fail.
As timing seemed to be a factor, I suspected OS or JVM threading model incompatibility with either the books code, WindowLicker or other components.
After a lot of head scratching a discussion helped me to identify two resolutions:
Upgrading from the r268 WindowLicker release on Maven Central to the binary available from Evgeni Dzhelyov's working solution mentioned above resolved the unreliable typing issue. It's worth noting that during a discussion with Steve and Nat, Nat suggested to potentially replace WindowLicker with Google's WindowTester library. I would have followed this advise if problems persisted.
Adding the IT suffix to conform with Maven convention ensured Maven execute the unit tests and integration tests in isolation. The previously robust end to end tests regained their successful execution and all was well.
During my completion of this project, I realised that documenting the issues I faced, and my solution to them might be of assistance to other developers reading the book.
If you are one of these developers and you do benefit from the above text and source code, I'd really appreciate feedback - please don't hesitate to comment :-)
In a word yes. After a few weeks of post work coding, lots of head banging and fist shaking, and after a couple of discussions with Steve, Nat and the Smack library developers, I'm now confident enough about the problem domain, and potential hazards to tackle the project in JRuby. In fact, I found sufficient challenge in the implementation that I'd like to have a go in Clojure at some point too; to see how the functional paradigm lends itself to this kind of evolutionary design, and stateful application development.
The next, and most probably penultimate blog post in this series will cover my Ruby implementation which I will publish this later in the year once I finish the implementation hopefully in collaboration with Andy Henson.