Behavior Driven Outside In Development Explained, Part 2
In Part 1 of this series, I walked through the development cycle of the "user views candidates" feature. In this article, I am going to implement the "user searches for candidates" feature, focusing on unit tests and refactoring. I will not show all the detailed steps - for a step by step walkthrough on implementing features following the BDD Outside In way, please see Behavior Driven Outside In Development Explained, Part 1.
Let's first write the cucumber feature: And run the cucumber test to see it fail. The test fails because it tries to fill in a search term in the "search" text field, which is not on the page yet. We will add that part of the page: We'll run the test, create a new route for searching candidates, and create the "search" action on the candidates controller. We are adding two exposures to get the search term parameter and ask the model the search for candidates. Again the test will fail, telling us that we need to implement the search functionality on the model level.Now we've dropped to the inner BDD circle again. Let's first write a spec to express how we expect the search to function. The spec has 7 examples, covering cases with one word, two words, partial words, empty string, and search with a multi-word last name. It is much easier and faster running to provide comprehensive test coverage for the candidate search function on the unit test level (Rspec) than to do it on the integration test level (Cucumber). Let's first let the test fail: It took me a few tries to let the tests all pass - I settled with a solution that I would create a new database column "full_name" which is automatically updated when candidate is saved with concatenated first name and last name. We will run "rake db:migrate" and "rake db:test:prepare" to migrate the development and test databases. The candidate unit tests now will all pass - also telling us that though we changed the way full_name is implemented, we didn't change its behavior.
Now let's jump out to the integration / cucumber test circle, and this time it will just pass. We can run the whole test suite again to insure that we did not introduce any regression bugs - one of the best benefits for the BDD, is that most bugs are caught right when they are introduced, and they are much easier to fix when the programmer still has the domain and context fresh in mind. In fact I can remember when I didn't do much testing it was not uncommon for me to spend at least half of my time fixing bugs (many bug fixes introducing new bugs!), while at Hashrocket I spend almost all my time implementing features. Now that all tests pass, it's time for refactoring ("Refactor in the Green", as the saying goes). The "list candidates" and "search candidates" features share a view, but we are relying on the presence of the search_result exposure to differentiate showing search results or all candidates and this is quite ugly. A better solution would be to make search a scope on the candidate model that just passes through if no search term is present. We can chain the search scope within the candidates exposure in the controller and remove the condition in the view.
The code is now much more succinct and the logic is simpler. We will run the complete test suite again to make sure that everything works and they do. The test suite we built up along the way gives us great confidence in the refactoring - so that we actually do it often and keep the code base clean. This is it! Leave some comments and let me know what you think.
The source code is here on Github.














