We went through theoretical parts of testing a project in the last couple of posts. First, we discussed the division of responsibilities across different company departments. Then, we moved into finding general characteristics of the code eligible for testing. And lastly, we dived into priorities of testing approaches and how to choose them for our project.
All of that was an abstract approach to the subject. We learned the main assumptions regarding the testing of an application. Today, I would like to start a four-part series on a more practical side of testing. To make that possible, first, we need some kind of project that everybody knows and understands. We need to design its main features and describe vital elements, so that later we’ll be able to apply automated testing.
We will move through different types of tests in future posts; we will cover unit, integration, and system-level validation. We’ll explain how other system parts may benefit from proper testing use-cases.
The mock project
Let’s start by designing a simplified system that will need a database, API, and frontend app. Say, we will mock a bookselling website. We need that to move through critical system elements and design proper test cases.
This designing exercise is similar to a rather popular architectural interview, where the candidate is asked to present a system that meets given functional and non-functional requirements. Right now, we aim to design only as much as needed to quickly decide where the vital points are and to devise automatic tests. We won’t spend much time thinking about potential user base or high availability issues. We won’t get into server architecture as well. A key thing for our illustration is the implementation itself and how to divide it into testable modules.
Suppose we need to design a web service that allows our users to do three main things. First, our user should be able to search for a given title or author. Then, after finding the book, the user should be able to find details about it. And finally, we want the user to add a book to a cart and then create an order for enlisted titles.
You must admit that the functional requirements are very straightforward, and creating that kind of service is not rocket science. However, there may be some interesting aspects of the system, and we shall talk about them now.
Let’s start by designing a database. We won’t do this very diligently because it is of less importance to us since our goal is to develop good test cases for the application. Having said that, it is usually a good idea to start by creating a fundamental data structure, so that we have proper nomenclature in place before constructing the API and user interface.
First of all, we should ensure that our database is relational. That will help us keep the proper data structure and presumably fully satisfy our needs. What would be the main tables and data in our system?
Well, we definitely need a
Book model, with
price defined. Then, we could use an
Author with their
name, and probably a
Category just to make our structure a little more complex.
Author are two models that should have a many-to-many relation with the
Book model. A book may undoubtedly have many authors, and they may write many books on their own. The same goes with the category relation. Whether to normalize the tables or not is not essential here, so we leave it at that.
The last thing that comes as a little twist is the search system. Surely, we need to be able to produce a list of books, preferably the most suitable ones, when the user searches for specific keywords. The keywords may be related to the book’s title, the author’s name, or possibly to the category or description of the book itself. There are many potential keywords that the user may search for, and each of them has to produce a comprehensive list of books. In addition, we may think about the issue of devising such a list, so that at the beginning, we have positions closely associated with the search; thus there exists a priority.
Let’s say that a
Keyword model with a given
word as a readable string could be a start. It has a foreign-key relation to the
Book and the alongside
precision number, describing how much a particular book is associated with the specific search word. We will get back to this model later.
This layer should implement a business logic that serves proper data displayed by the user interface. We choose a REST API that will implement three primary endpoints querying the database and responding with a JSON payload.
Those three mentioned elements are
createOrder. Let’s see what they do one by one.
Listing is a GET request to fetch books filtered by the received
query parameter. Books should be listed from the ones that are related to the given keyword the most. Getting a book is a GET request to fetch a single book by ID. It is a simple query that fetches an upright row from the
Book table. Creating an order is a POST request to send a list of ordered books alongside the personal info gathered by the order form.
The API service is the first suitable place to introduce helpful test cases. In the future posts, we will dive into details of such a validation, while today, we’ll present a brief description of some system elements that could be tested later.
Listing books seems like the most daunting task, so let’s think about it for a second. Suppose we send a request with a specific keyword, and as a result, we expect a complete list of books related to that search. We have a model of correct keywords in our database. Each of the words has its
precision and the referenced book. We also have a list of books in the database. What would be the most straightforward algorithm to generate the list from the keyword?
It is, in fact, pretty obvious. We can filter the
Keyword table by
word we have received from the GET request, then sort them by
precision values and return IDs of books that are the most relevant from the perspective of the given word. Finally, query books by IDs and prepare the response to be sent.
Easier said than done, but the point is to mark specific aspects of a system that would benefit from automatic validation. Can you think of any other API parts where testing would be essential?
Now, it is time to discuss the user interface and what kind of testable implementations we can find there. The frontend web service should have at least three separate views.
We need a list view with the search form to type keywords in and reload the presented list. We also need a book-detail view to show a description of the book, its author and maybe to display similar positions from the same category. And lastly, we need a cart view to summarize our choices and see how much everything costs. We can fill the order form and purchase the books in this view.
Again the question is, what are the most critical elements of the frontend application? We will find some meat in the search form, because the very idea of parsing user input and directing the app to the proper list view may not be a trivial thing. In addition, we have to remember things around the list like H1 tags or meta descriptions. That may be critical for our SEO purposes and thus a valuable thing to verify.
Placing an order from the cart view is another critical element of our system. It may seem effortless in general, but there are some intriguing pieces in the details. Our functional requirement is to keep the selected books in the browser memory, such as LocalStorage, then fetch the data and present a complete summary. We can expect that maintaining those elements in sync will involve some engaging code. The further we move into details, the more fascinating bits we encounter.
Today, we designed more or less accurately a system of an online bookstore. The application has many intriguing elements, which, when considered in detail, reveal vast possibilities for implementation and improvement. It is an excellent opportunity to think in breadth about different cooperating services and in-depth about the internal performance of every one of them. Try to spend some time considering what parts of the system are the most critical and where you would place automatic tests.
We will dive into specific test cases in the next few posts and see how creating a multi-level verification works for our mocked project.