How many times have you tried to plug in a USB cable and failed on the first try? If you’re like me that number is nearly the number of times I’ve ever attempted to connect a USB device. How many times has this happened with a network cable?
While most people have heard of the term kaizen, or continuous improvement, when talking about Toyota and it’s lean manufacturing principles, fewer have heard of kaizen’s unsung sibling poka-yoke, or mistake proofing.
The main idea of poka-yoke is to draw attention to human errors as they occur, so they can be corrected or prevented, reducing the number of defects in a product.
After observing a defect, we identify the root cause of the mistake, and then apply a fix or change in process that attempts to prevent the same error from happening again. This learning can then be applied to improve the design and function products.
Building your products with poka-yoke requires more initial effort, however the end result is a higher quality and more reliable product.
As developers, our tool to poka-yoke our services are unit tests.
Tests are small pieces of software that exercise a piece of code and verify that it’s behaving as intended. Writing tests are, however, many developers least favorite thing about creating software. We like to build new services and libraries because they’re fun and there is a sense of immediate gratification.
While writing good tests is time consuming and not fun. Setting up the scaffolding, defining test cases, and setting the test data can feel like tedious grunt work without an end in sight. However boring tests may be, they are essential to quality software.
With Kwoosh we’re aiming for high reliability, minimum regressions, and fast operations. Part of our testing strategy is to have “workflow” integration tests. These tests aim to help make certain that our tests are comprehensive while being easy to understand.
Each test currently has a maximum of two tests named ‘test_get_workflow’ and ‘test_post_workflow’. Each test then calls a series of descriptively named helper functions to assert behavior.
def test_get_workflow(self): # Make sure that anonymous users are redirected to login # and that users are denied access to data that's not theirs self.redirect_for_anon_get() self.not_found_for_wrong_account() # Check that our filter interface is filtering data properly so # calls to "mine" only show tasks assigned to me and so forth. self.filters_response(self.sub_task_data['title'], 'all', ) self.filters_response(self.my_task_data['title'], 'mine', [self.sub_task_data['title'], self.completed_task_data['title']]) self.filters_response(self.completed_task_data['title'], 'complete', [self.sub_task_data['title'], self.my_task_data['title']]) def test_post_workflow(self): # Make sure that all post requests are redirect # or return 405 and denied self.redirect_for_anon_post() self.post_not_allowed()
As a lot of the tests will have some helpers in common (such as confirming anonymous users get redirected to login), we can bundle up these helper methods in small classes and include them into the tests, reducing the amount of code we need to maintain.
Less code = Fewer places for bugs = Less maintenance
Having descriptively named helper methods will allow future me to glance at the test I wrote today and identify exactly what’s being tested, without having to read through hundreds of lines of code.
Quality isn’t many things. Quality isn’t free. It isn’t flashy. It isn’t fancy. And it sure as hell isn’t an accident.