5 ways in which we increased test efficiency by 41%
Last month I participated in a hackathon which had a two-week sprint cycle, and the objective was to complete functional testing of an application in a behaviour-driven development model. I was part of a five-member cross sectional-team consisting of a developer, testers and a product owner who was also expertly managing a set of teams. It was an intense, focused, time-bound, elaborate task which extracted the best of our abilities and stretched the limits of our team’s commitment and dedication to delivering the final output. In hindsight, we were able to filter out certain factors which helped us in saving a few precious seconds of time while executing our test suites and identify certain ways in our working style which finally pushed us over the finishing line well in time.
The elixir of Given-When-Then
Writing production level gherkins
The vocabulary of given-when-then provides a homogenous understanding of the feature being developed to all the stakeholders involved in product development, including the testers, developers, product owners, and end users. The client may describe the project needs in a detailed or succinct manner dwelling on their overall expectations from the project. Writing production-level gherkins helps translate those ideas into definitive use-case scenarios with accurate descriptions of the logical flow expected from the components of the application. It provides a complete and precise understanding of the path to be followed by the user and helps write clean production-level code.
Combining shared features together
Once the user is logged into the application, the subsequent pages will share a common feature of ‘Given the user is logged in.’ We used the background keyword to describe a common Given functionality instead of repeating the same step in every scenario. Once the user is logged in, when the user is on page 1, the subsequent actions on that page like selecting checkboxes, checking records on that page or sorting a table in any order will all share the same common Given and When feature definitions. Chaining these two steps together in a shared feature file, helped us remove duplication in generating step definitions, and reduced the time taken in executing these steps.
Writing clean code
To complete the UI testing, we identified various modules in our application which were then allocated to various team members. Our team had routine stand-up calls every day to discuss our progress, identify any challenges that we were facing and accordingly, plan the roadmap ahead. After writing the first 20% of test cases, we quickly realized that we needed to follow certain practices while developing our code base. We had difficulty understanding what the other team member’s code was trying to do, what the variable names implied and the linkage was not clear. We discussed as a team and decided to consciously follow the following practices for an easily understandable, maintainable and clean code:
First, we created a common utility file where we stored static constant variables for commonly used reference variables with String values, and some commonly used methods.
Second, we identified common methods used in unit testing and called on those methods for integration testing, like validating assertions, checking page titles, and verifying footer messages.
Third, we used pico-container dependency injection to enable loose coupling between classes. This means that when we had two Classes, Class A and Class B, and we wanted to use the methods of Class B in Class A, then instead of creating an object of Class B in Class A, which is the usual method, we created a Constructor (public) of Class A and passed Class B as an argument. This allowed us to use all the methods of Class B in Class A. Based on the requirements of our project we had to create a Class C which extended our Base Class but the principle of using the dependency injection was the same.
Fourth, we made a clean project tree structure and saved all our files in src/test/java and src/test/resources.
Fifth, we followed proper naming conventions and formatting standards. We provided meaningful names to reference variables and followed defined naming conventions for Classes, variable names, and packages. We provided appropriate indentation and line spacing while writing the code. We also used comments to explain what we were trying to achieve in the specific code.
Sixth, we used data parameters. Instead of hardcoding values of variables, we used a scenario outline and excel file with Apache POI library for method reusability and maintainability.
While trying to learn how to write clean code, we realized that Eclipse has an automatic code formatting option available in the code editor. You can use that option to help you or customize it as per your requirements.
In Eclipse go to Windows...Preferences...Java...Editor...Save Actions...Click on Perform the selected actions on save...Click on Format source code.
Unit Testing early on
At the beginning of the hackathon, we were all busy writing gherkins, generating step definitions and designing unit test cases. We realized that testing the bulk of our scenarios towards the end of the release cycle could create some issues for us. This is because if we found defects further on in the development cycle, it would take us longer to fix them and that might delay our product release. So, we tried the approach where we picked up each scenario, designed its unit test case, automated it, analyzed the test result and logged the defect report. The defect reports acted as the requirement document for the developers to work on and fix them so that the application could perform as per user requirements. Analyzing the success and failure scenarios early on helped save a tremendous amount of time for us when we did integration testing of various modules. For instance, when the login module worked successfully, the integration between the Registration and Login modules also happened without any major defects/blockers. We conducted parallel testing through TestNG where we executed the test cases parallelly rather than one after the other. When we compared the tests run sequentially to the output generated after running the tests parallelly, we realized there was a reduction in the total time taken in test execution by 41%. It was not only parallel execution but all the factors mentioned here in totality that helped us in reducing our total code execution time. This cycle of ‘Writing gherkins, Generating steps, Writing code, Test execution, Logging defects and Repeating this process,’ worked tremendously well for us in completing our project in the given sprint.
Using Agile practices
The scrum followed to manage our project accelerated our communication and feedback loop. Our product owner created cards in the Trello board and JIRA for real-time tracking of progress being made and for assigning responsibilities in a clearly defined manner. Moving cards from in-progress to done status also helped us in prioritizing test cases and it gave us a visual representation of the scope of tasks present in front of us. The comments description in each of the cards helped in seamless communication among the team regarding challenges being faced by any team member. The daily stand-up meetings acted as a refreshing warm-up exercise for us to buckle down and get started on the remaining tasks for the day. As the name suggests, the agile process provided us with the required flexibility while also emphasizing a broad outline in which we were supposed o complete our work.
While doing the project, we felt that the role of a software development engineer in test easily permeates into the role of a test engineer at some levels. As part of the hackathon, we were all writing code and executing tests, but at times, the team lead took on more of a role of a test engineer. She focused not only on executing the automation test of her assigned module but also on the optimization of the project structure, speed of execution, improving the code coverage and focused on everyone writing production quality code. Some of our team members huddled together and carved out time to explore automating the test suite in a second programming language besides Java. Somebody who had been able to complete test automation of their scenarios helped others in writing integration tests, thus saving on the total time taken by the team to complete the project. A mandatory part of every project that is successfully executed is excellent documentation. This could take the form of making presentation slides, recording progress charts, sharing documents with product owners, taking notes, or researching reference materials. As the sprint progressed, the team members gravitated towards different areas of work left to be completed in the project rather than concentrating on their individual assignments. This brought synergy to our work, and we were able to tick all the to-do boxes on our checklist in a cohesive manner.
Our team came together as a team of SDETs to complete UI automation, but we naturally graduated from writing and executing tests to contributing towards the product development effort also. We tried to develop a healthy code base and started automation testing early in the testing cycle. We worked cohesively as a team, contributing our skills and complementing each other’s efforts. The agile environment provided us with the required flexibility and enabled us to prioritise our tasks. All these factors helped us in improving the efficiency of our test output.