Today, we are going to talk about system testing. It is the highest level of our three-tier automatic testing levels. In the previous posts, we discussed unit and integration levels, which are generally easier and faster forms of validation.
Let’s start with the main definition and specify boundaries between tests granularities. System testing should involve a full system verification. Whether it is a development, staging, or production environment, we are talking about checking functionality and other requirements in the complete application. When our application is a web service, we usually need a web driver like Selenium to perform automated interactions.
In comparison with integration testing, system validation does not use mocked parts of the system. It also does not focus on specific modules but instead drills through the application end to end.
Another curious thing is the terminology. System testing is often called end-to-end testing (E2E). That discrepancy is tricky because both terms usually perfectly describe what we are trying to achieve, so I believe we can use it interchangeably. However, if we are going to dive into a stricter characterization, in that case, I think we can agree that depending on the interpretation of the word “system”, end-to-end testing fits into a span of vast system validation. Thus, system testing seems like a more flexible term because it could reference small services and huge combinations of services, tested end to end.
Frontend and detail view
When we define a system as the entire app, there is no way to distinguish between frontend and backend. All validations concerning only part of the system would be regarded only as integration testing.
To find the most suitable places for system testing, we should walk through all frontend views and think about key functional needs and technical requirements that we can verify.
Let’s start with a detail view. Truth be told, there is not much that we couldn’t verify here via lower-level testing. Remembering the granularity rule, we should start from the lowest level testing and consider a higher one only when we couldn’t handle specific cases. High-level testing is powerful, but unfortunately, this power comes at a cost. It needs a unique setup, and its execution takes more time.
We can implement a system test that verifies title, metadata, and other generated texts, but if we are able to do the same with the unit and integration testing, then implementing system tests for that purpose may be unnecessary.
Another exciting concept is to verify the cart functionality. We remember that the page containing book description should also render the “add to cart” button in case the user decides to purchase the title. However, system testing is more about flow verification; thus, we will get back to this later when discussing user behaviors related to cart view.
System testing allows us to validate the user’s entire interaction path. It could encompass many activities on different views that together represent the functional requirement for the application.
What could a full functionality test look like? An excellent example is a search form. First, we render the page with a list of books. Suppose that at the top of the list, the form is rendered. Verifying that everything is in place may be our first assertion. Then, filling the input with “charles dickens” and hitting “search” should redirect us to the
/s/charles-dickens/ URL. A fresh list of books appears on the screen. Following assertions should ensure that we have a new list with proper titles in place.
This simple test validates the critical functionality of our application. The user is able to enter the page, search for something specific, and find positions that are related to the search.
An additional test may verify specific cases after using the form. For instance, we can check whether the empty list message renders accordingly when the searched keyword has no corresponding results.
Another test case could ensure that the list view cooperates properly with the detail view. Let’s say that we start with a list of books and verify the positions on the list. Then, we may trigger an item click and end up on the detail view. In this situation, it is beneficial to ensure that we landed on the right page and that the list item we clicked has the same title, description, and other data rendered after the redirect.
Testing a click may seem trivial, but from the business perspective, entering a list, clicking a specific item, and reading about it more is absolutely a top requirement that should never break.
The thing with system testing is that we can design the script very flexibly. We may create short user interaction circles and long, complex ones. It should all depend on how we expect our users to behave and which interactions are essential from the technical and business standpoint.
The more interactions the view offers, the more possible system tests we can design. Still, we will focus on the most beneficial use cases.
The critical functionality related to the cart view is the logic of the cart itself. The test may be designed in many different ways, but what we are trying to do is to enter a page, add an item to the cart, and then verify whether the cart actually displays the chosen title. We can also test whether the total is calculated correctly. Adding to the cart may be possible from the detail view of the book, as well as from the list view; it depends on the user interface. Nevertheless, validating the key functionally that leads to the purchase is extremely important. As a side path, we could verify removing titles from the cart and focus on other cart functionalities crucial for business.
The last system test that is incredibly important is sending the order. We can all agree that it is a crucial element of a successful online store. Hopefully, we already have some unit and integration tests related to this functionality. Still, having a high-level validation will ensure that the end-user can actually place the order.
Let’s start with having some titles in our cart rendered in the cart view summary. Below there should be a form to create an order. Inside the test case, we can trigger filling the form with valid data and hit send button. In the success path, we should test whether the form has been sent and whether the communication with the user is complete. In the comprehensive system test, we can verify the confirmation email or check the place where new orders are stored. All of this depends on how exactly our functionality works and what is the best way to ensure it works well.
Of course, in many cases testing only a success path is not enough. To make sure that the form is functional, we should also verify the error communication. How does our form handle the failed request, and whether the user gets enough information to understand the application’s behavior?
Today, we end our journey with testing software. In the last four articles, we went through the practical path of designing a testing suite. Creating a mocked system and selecting places for unit, integration, and system testing is an excellent way to learn the testing fundamentals. In the recent articles, we referenced the previous three-part series describing the overall testing theory. Every application is different and has different needs, so there is no general approach to testing software. However, we should always think ahead and define what to test and how to do this properly.
I hope that will help you design your own testing suite. Cheers!
Sidenote: From now on, I will publish a new post every two weeks. See you then!