A while ago, I found myself roaming the new and strange waters of Quality Assurance. Writing tests is a part of the development process, so no matter what technologies are used, sooner or later you learn that testing your code is necessary. While working on Front End, I had to learn really quickly that, “Testing shows the presence, not the absence of bugs.” This really got me wondering just how important my role as a QA is in assuring quality and does it rely solely upon me and the QA team to make sure everything works?
In this blog, we will take on a journey exploring the fundamentals of QA, introduce the terms and practices and take a look at two different approaches to development that incorporate testing in the developer’s workflow.
“Quality is achieved by putting development and testing into a blender and mixing them until one is indistinguishable from the other.”
How Google Tests Software
Let us start by introducing our key players in the game called “The Software Development Process.”
For this game, we will be focusing on two players of the game (the developer and the tester), even though the roles and responsibilities behind each software development project range far and wide and there are many more key stakeholders than just two. Meet the two players:
Player #1: The Developer aka The Problem Solver. Developers are the ones building things, putting the pieces together and working out the logic that connects everything and drives the software in the background.
Player #2: The Tester aka The Critical Thinker. Testers are all about analysis. Once the developers build the product, the tester takes it apart and inspects it piece by piece.
The fundamental difference between testers and developers is in the process. The developer synthesizes the code into a product and the tester analyses it. Both testers and developers like figuring out how things work, but the difference is where the developer is focused on putting things together to make them work in a certain way and the tester is focused on taking things apart to find out all those unintended consequences. Testers and developers are the yin and yang of the development world.
The goal behind software testing is to check whether or not the tested software system is defect-free. It is a process that evaluates the functionality of the software and it matches the expected result or behaviour with the actual one. Software testing is used as an objective source of information about the quality of developed software. In software project management, the process of checking whether a software system meets its required specifications and that it fulfills its intended purpose is called verification and validation.
Verification and validation, or simply software quality control, is a part of the software development lifecycle. We use verification to ask the following questions: "Are we building the software in the right way?" and "Are we correctly implementing specifications in our system?"
Validation, on the other hand, asks questions such as: "Is the right software being built?" and "Does the system that we are building match users' needs?"
To verify and validate software we use test cases. A test case is a detailed test instruction that defines the inputs, procedures, execution conditions and the expected result of a test. A collection of test cases intended to be used to test the software system is called a test suite. A test suite groups together similar test cases as they grow and expand according to an already determined test plan. A test plan is a detailed understanding of objectives, resources, and processes needed to test the system, therefore, a good test plan is crucial to the development process.
When defining testing, we must acknowledge that there are different levels of testing. Tests are grouped into levels depending on what the focus of the test case is and the specificity that the test case covers. They are also defined by when the particular test is added to the SDLC. There are four levels of testing:
Unit testing focuses on the smallest testable part of any software, such as modules, classes, and components. Unit testing isolates units of code from the rest of the application and tests them individually and independently. Unit testing is done by developers as they work on the app.
The goal behind Integration Testing is to find defects in the interaction between two or more integrated components. Integration testing makes sure that units work together in cohesion forming whatever functionalities we need. Integration testing is mainly done by quality assurance engineers.
System Testing is performed on a complete and fully integrated system. It is a black-box testing technique which means it doesn’t concern itself with the inner workings of the system, just that it is compliant with its specified requirements.
The purpose behind Acceptance Testing is to evaluate that the system does not differ from the business requirements and assess whether it is acceptable for delivery. It is done pre-release and sometimes it is called Operational Acceptance Testing (OAT). OAT tests security vulnerabilities, recovery procedures, functional stability, supportability and reliability of the app.
Test-Driven Development is a methodology of developing software in a manner that expects you to first consider the requirements for the code that you are about to write. Also, instead of writing the logic right away, the developer would first write the tests. TDD starts with writing a unit test before writing any code. This test would first fail and then the developer would write just enough code so that the tests would pass. Next comes refactoring and improving the code. To explain TDD, we often use the Red-Green-Refactor scheme that indicates that the purpose of TDD isn’t only in test coverage, but also in safely refactoring your code, not fearing change and merge. The following diagram displays the Red-Green-Refactor pattern:
Most developers find TDD unnecessary and a waste of time but what “Red, Green, Refactor” means is that at any randomly chosen time in the development process, unit tests are there to make sure that all of your code works. Unit tests increase code coverage but most importantly ensure that the software is tested in-depth since they serve as a guarantee that all of the pieces that make your code tick are doing what they are supposed to be doing.
In a continuous integration environment, unit tests should run every time you commit a change to the source code repository and they should also be run on the development machine as well. Let’s take for example an app component like email input. This input should adhere to certain rules. Maybe it should display an error message if the input was left empty, or if the string was not a valid email address. These would be two test cases that the unit test should cover. Using the TDD approach, the developer would first write these unit tests, run them so they fail, then write the component code to satisfy the test expectations.
Behaviour Driven Development has the goal of bridging the gap between business and IT. BDD puts the development focus on the behaviour of the app, or the feature that you are making. In BDD, assertions and making sure that something exists or that two values are equal are merely one aspect of behaviour testing. Scenarios for these tests, on the other hand, are full software specifications and can also serve as living documentation for the application. Behaviour scenarios show how the app is supposed to function in a humanly readable manner, thanks to Gherkin.
What is Gherkin? Gherkin is a business readable, domain-specific language used specifically for behaviour descriptions of app features. Using abstractions, it removes logic details from behaviour tests. Gherkin is designed to be easy to read by non-programmers and uses the Given-When-Then step structure to illustrate how a feature should behave once it is presented to the end-user.
While TDD focuses on how a single functionality or unit is implemented, BDD focuses on integrating these functionalities into behaviour from the end-users perspective. Similar to the email input unit test example, the BDD approach would expand the test case to define a complete feature scenario, for example, an entire log in check.
Gherkin scenario for log in behaviour:
Given user is on the log in page When user enters “email@example.com” as email address And user enters “password” as password And user click on the "Login" button Then user should login successfully.
Or to add even more abstraction since whoever is reading it shouldn’t be concerned with what the login steps entail, just that they are executed successfully:
Given user is on the log in page When user logs in Then user should login successfully.
Among testing trends, there seems to be a crucial shift towards the beginning of the software development cycle. This allows us to find issues early on in the process and to fix them. Shifting testing towards the beginning relies heavily on making developers responsible for testing and practices like continuous integration and delivery which in turn require automated tests that can be run quickly and reliably. This shift also recognizes that making testers the only ones responsible for creating an automation framework and writing tests is going to slow down the development process.
In the book, “How Google tests software," a similar inquiry was made:
“At Google, this is exactly our goal: to merge development and testing so that you cannot do one without the other. Build a little and then test it. Build some more and test some more. The key here is who is doing the testing.“
The text continues to answer the question, "Who better to do all that testing than the people doing the actual coding?"
Behaviour-driven development practices and the test-first development approach help split the testing into layers. Before the code is written for a new feature, the developers should have the solution planned out with automation in mind. Using both BDD and TDD would test the quality of the code the developer writes and test the behaviour of the system defined by the product owner.
Software products should always be made with testability in mind. From the product owner to designers, everybody should be critical of the software they are in the process of producing. Every bug, visual bug, and grammar mistake should not go unnoticed when aiming to ensure software quality. Testing shouldn't be an activity that is done only at the end of a sprint by a designated tester.