Understanding Software System Testing: A Simplified Approach
Written on
Chapter 1: The Basics of Testing
Testing can be a complex skill to master. It requires considerable thought and experience to grasp effective testing techniques. New developers often encounter phrases such as “Avoid testing implementation details,” which may not clarify the concept. What does this mean, and why is it important? To elucidate these fundamental principles, let’s visualize some concepts that can aid in comprehending effective testing strategies.
A System
For our discussion, a system can be defined as any entity that processes inputs to produce outputs. This could represent a function, a component, or an entire application. Essentially, systems comprise subsystems that can further encapsulate smaller subsystems, creating a hierarchy.
A Function: createGreeting
Let’s examine a simple function, createGreeting.
This function takes a name as input and produces a greeting as output.
While straightforward, this function is a system worth testing. Before diving into tests, let's explore how to evaluate any system effectively.
Testing a System
Using our basic model, we can identify inputs and outputs for testing. The testing process involves mocking inputs and verifying outputs through assertions.
In simpler terms, we can manipulate the output by controlling the input. For instance, if we consider a light switch and a bulb as our system, toggling the switch (input) should toggle the light (output). By controlling the switch, we can assess the light's response.
Testing a Function
Now that we have a grasp on testing systems, evaluating our function should be straightforward. If we mock the input as "Chris Jeffery," we can assert that the output is "Hello Chris Jeffery!"
The input can vary; I chose my name for this example. In JavaScript, the corresponding test could look like this:
I’m using Jest for this illustration. While we can set aside the describe block for now, the focus should be on the test body. Within the test, we invoke the createGreeting function with 'Chris Jeffery' and verify the output. Upon executing the test, we’ll determine its success or failure.
Testing a Component
Next, let’s tackle a slightly more complex example: a <Button /> component.
We will examine whether the button is clickable. If it’s enabled, the button should respond to clicks; if it’s disabled, it should not. This is achieved by mocking the click handler, handleClick, and verifying whether it was called after clicking the button.
Bad Tests
While we’ve discussed how to write effective tests, it’s equally important to recognize poor testing practices.
Avoid mocking anything within the system.
Mocking internal elements of the system can result in false positives, yielding tests that never fail. This issue arises when we provide the expected outcome instead of allowing the system to operate independently. Consequently, a test that consistently passes is rendered ineffective.
Avoid asserting anything inside the system.
Similarly, asserting conditions within the system can lead to false negatives. You may have heard, “Do not test implementation details,” which relates to this idea. A false negative occurs when we assert something that changes within the system, resulting in a failing test despite the system functioning correctly. Although a single flawed test might be manageable, numerous such tests can severely hinder development speed.
To illustrate this further, consider a scenario where a person requires a heart transplant. We might write tests to ensure the individual remains alive before and after the procedure. If a developer mistakenly checks that the heart is genuine, the test will fail after the transplant, even if the individual is still alive. This example underscores how false negatives can occur when the system's state changes.
Conclusion
In summary, writing tests boils down to clearly defining the system in question. We’ve explored how to evaluate functions and components in these basic scenarios. The key takeaway is to only mock inputs and assert outputs. If we mock or assert within the system, the tests may become ineffective, complicating future updates.
While things can become more intricate, it’s essential to think critically about what constitutes the system. The boundaries can sometimes blur, but there’s always a way to delineate the system’s components. This model remains applicable in all cases.
While we concentrated on crafting effective tests, future discussions will address what to test. For those eager to learn more, I recommend Kent C. Dodds’ Testing Trophy article.
I hope this guide simplifies your understanding of testing.
In this video titled "What is System Testing in Software Testing? SoftwaretestingbyMKT," you will gain insights into the essential aspects of system testing and its significance in software development.
The video "AWS re:Invent 2020: Testing software and systems at Amazon" provides a deep dive into Amazon's approach to software and system testing, highlighting innovative techniques and best practices.