Suppose You Want to Prove That Your Code is Working and Start Writing a Unit Test for Your Program
You gaze through the window lazily, watching the rain fall and ignoring everything else. You've been like this for the past 10 minutes. Instead of words, doodles find their way onto the paper you should be working on. You've tried to start writing, but you lose motivation a couple of seconds in. This cycle repeats itself.
The weather had been awful since the morning. The office's mood was not so good either. You had seen nothing but gloom. The manager had a fierce debate with Rory about the team performance. Riley, as usual, came 1 minute before work time, not making Rory's mood any better either. You could clearly see the annoyance on their face. On the other hand, Alex... On second thought, forget him, he just flexed his wet clothing from the harsh morning's rain. You couldn't understand why he was so proud of it. As if it wasn't bad enough, just after you sat in your chair, an email popped up. It was from Rory. They told you that the tests you have written aren't good enough. They didn't cover every case possible, Rory explained. Even worse, the email they sent also asked you to do more work.
It was as if Rory asked you to do better in the subsequent work. You took a piece of paper and started writing possible test cases, hoping this time it would be enough to satisfy your ill-tempered team leader. But the more you tried to write, the more hopeless it got. You realized the possibilities are too much to cover. Some cases were meaningless, and some needed to be covered. However, you couldn't give a proper argument as to why some were more meaningful than others. Somehow you found yourself in this endless loop.
Now, you try to look at the paper you have ignored for the last 10 minutes. It is a mess no matter how you look at it. You revert back to a 'no thoughts, head empty' state. The doodles on the paper do not represent anything at the moment. They are purely frustrations, a byproduct of your inability to discover the solution. You then decide to look back at the water drops outside. With that, the loop is restored.
You focus on the relaxing sounds of the rain, so much that you don't realize a pair of eyes has been looking at you. Surprised, you start to look toward the subject. Emory is sitting on the empty chair next to yours, looking bored and, as usual, without any trace of expression.
" How long have you been there?" you ask. " For 5 minutes, I think. "
The conversation ends there. You and Emory simply stare at each other without uttering a single word. This awkward situation lasts for several seconds before Emory starts to ask:
" Is there something wrong? You just sigh at the paper, then laugh like a maniac, then look sad, and gaze away at the window. " " Did I really do all that? " Emory nods. " Did Rory ask you to write tests again? " " It's scary that you are always able to guess my problem every time.." " So, what is the problem?"
You then explain everything to Emory. The whole thing takes you about 3 minutes, and Emory's best response is just a harsh comment:
"Yeah, I can guess since your past tests are not that good. " They say.
It definitely hurts your pride to hear that. The comments from Rory is bad enough and hearing this from Emory does not help you at all.
" I can help you, though. " " Really? " " But... " Emory stops there, pointing at the clock beside your table. " You owe me lunch. Again. "
Input Space Partitioning
"This is a concept I learned in my university back in our homeworld. It's called Input Space Partitioning. I can see you have understood that writing every possible case is not feasible. For example, data types such as String and Integer have infinite possible combinations. There is no way to cover all of that. But you can build a model that represents the test cases you want to make. This model will guide you in creating concrete test cases by choosing a representation of the input domain. " Emory takes a paper from your desk before further explaining the concept.
" Input Space is simply a domain, a space of possible input to the program. For example, integers input may be a positive number, negative number or a zero. String can be a length of three or simply called non-zero input, a length of 1 like the character 'a' or even an empty string. In general it's all possible input for the program. The problem is the possibilities are limitless. That's what happened. This is the problem you were facing. The next question is, 'how about not inputting all the possibilities?' Just the good enough number of test cases to cover most of the input space? The answer to that question is here" Emory starts to write the concept on the paper. Then Emory writes a sample program to demonstrate the concept.
"Input Space is simply a domain, a space of possible input to the program. For example, integers input may be a positive number, negative number or a zero. String can be a length of three or simply called non-zero input, a length of 1 like character a or even an empty string. In general it's all possible input for the program. The problem is the possibilities are limitless. That's what happened. This is the problem you were facing. The next question is, 'how about not inputting all the possibilities?' Just a good enough number of test cases to cover most of the input space? The answer to that question is here" You start to write the concept on the board. You write a sample program to demonstrate the concept.
1 2 3 |
|
"Suppose you want to test this code. Both inputs are integers. Which means we will work with the input space of integers. How many integers are present in this input space?"
"Infinity. From minus infinity to plus infinity" you replied.
"Correct."
Input Space | Positive Integer | Negative Integer | Zero |
---|---|---|---|
Example Value | 7 | -30 | 0 |
"Like what I have said earlier. Integers can be categorized as positive integers, negative integers and a zero. Now look at this table!" Emory is pointing at the table on the paper they just wrote earlier.
"We can choose the sample value of each category with this method and then create test cases using three values. This three value has represented the large amount of space on the integer domain. So we can create tests as follows."
1 2 3 4 5 6 |
|
You are impressed, but you have a question to ask. "This concept is truly fascinating. But how can we use this concept for a more complex system? Can you give me more examples?"
Emory responds silently. They take another piece of paper. " This time I am thinking to show you the example of Palindrome checking to demonstrate another concept of ISP. "
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
"Now we have this method that tests whether a string is a palindrome or not. Input is a string. So we will work on the input space of string. When I showed you the example of integers, you might realize that I divide the input as types of integer values. This is called characteristics, and in this case, the characteristics of integers are their relation to 0. How we create the 'characteristic' is free, and will depend on context. For this palindrome example I will create two characteristics. One is similar to the example of integer earlier, is created based on string properties of length on the their relation to 0. Another is considering the behaviour of the program itself. In this context we know the program can detect a palindrome. So we can create another characteristic: The properties of string in their relation to palindrome. Look at this table."
Characteristic | Block 1 | Block 2 | Block 3 |
---|---|---|---|
Relation to length | 0/Empty String | Exactly 1 | More than 1 |
Palindrome | Palindrome True | Palindrome False |
"So with this characteristic, we can create test cases. One thing to remember about creating a characteristic is a block value can't also be a member of another block in the same characteristic and it must hold a complete member of that characteristic, the completeness. For example the characteristic of length. A string can't have length of 0, 1 and more than 1 string at the same time. The second block also holds this property. A string can't be both palindrome and not a palindrome at the same time. Both hold all possibilities of the characteristic, so It is also a complete partition. "
"Now let's create the test cases from these two characteristics. A block from a characteristic must be paired with all blocks from another characteristic. Look at this example. "
Palindrome True (B1) | Palindrome False (B2) | ||
---|---|---|---|
0/Empty String (A1) | "" | (Infeasible) | |
Exactly 1 (A2) | a | (Infeasible) | |
More than 1 (A3) | abba | Lost |
You stop Emory's explanation and raise a question. " What is the meaning of Infeasible you wrote in the third column on the first and second row?"
"Simpy they don't exist." Emory replied. " All empty string by definition is a palindrome. And so does a string with length 1. Meanwhile you can create a number of examples of the third pair. " Emory stops and take another piece of paper to write.
1 2 3 4 5 6 7 8 |
|
"I want to show you one final example of ISP" Emory then proceeds to browse your computer to check for a suitable codebase. "I think this one will work," they think.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
create
method.
"As I said earlier, there are two ways to create a test requirements: Natural characteristics of the input space, like my first example. Or you can choose to create the characteristic from your knowledge of the method like my second example. As the program you test get more complex, you will use the latter more often. I will give you an example. "
Characteristic | block 1 | block 2 |
---|---|---|
Name Duplication | Duplicate (A1) | Not Duplicate (A2) |
Data content | Empty (B1) | Not Empty (B2) |
Name charset | ASCII standard (C1) | Non ASCII standard (C2) |
"I created three characteristics: Name duplication, data content, and name charset. Name duplication is refer to the possibility of duplicate name that prevented in validateName
method. We also need to check two conditions of the database to ensure both condition's output is correct. The last one is to check the case user will input non-standard characters. Such as specific language characters.
As you can see, I didn't use specific domain at all. Instead, I used the knowledge of the method to derive the charactristics and blocks. This mechanism to derive the characteristics is called Functional-based approach. The previous example that used the input specific domain is called interface-based approach. Now let's create the test requirements. "
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
"Do you undertand what I have taught you until now?" Emory asks, as they want to confirm your mastery of the subject.
"I have one more question. "
"Go on. "
"So, a block needs to be paired with all other blocks?"
"Correct. This rule knowns as 'Pair Wise'. There are some other coverage criteria in Input Space Partitioning. You can learn about them if you like. I usually use pair wise."
You are relieved. "This is indeed a good way to reduce the test cases and create quality test cases." you said.
"Now, shall we apply this concept to our system?" Emory proposes.
"Of course. Let's begin."
Writing Unit Test with JUnit 5
“Now we have the concept, let’s work on the system. Can you tell me, what system do I need to test first?” After a quick break, you are ready to test the system. However since this is not a system you have known before, you need to find out the behaviour of the system. You then read the email sent by Rory to take a look at the codebase and some general information about the system.
From what you know, this application has two very basic functions. It can show the list of registered adventurers and it can register a new adventurer to the system. Business logic is located in the service
package. There is one model used in this system and belongs to the model
package. To control and communicate with the frontend, there is one controller
class. Rory said that you should start with the business logic first. So it means you need to test the service
package.
Now you know what to do. As you know, you can generate the test class using the IDE assisted feature. If you are using IntelliJ, you can press Alt+Enter
on the class name to open the sub-menu. Choose Create Test
. Select JUnit 5
as the testing library. You can do some experiments with JUnit to test the application.
Tasks
- Read this document to understand the requirements.
- Document the process of creating Input Space Partitioning of all methods available in
AdventurerRegistrationServiceImpl
class. - Create characteristic and test case value candidates similar to the example provided in this document (see: Palindrome and StudentService), which is in tabular format.
- Write your documentation in README.md.
- Create JUnit 5 unit tests based on the test cases you have created.
Hints
- You may try to run the application first to understand the behaviour.
- Do some research about JUnit 5.
- Use the IDE assisted feature to help you create and run the test.
- In the process of creating characteristics for Input Space Partitioning, the input domain can be related to the method you want to test, not just the input value. Example in the palindrome there are 2 blocks, one representing input space of the string and the other one representing possible outcome of the method. You can try to apply the same strategy here.
References
Created: 2022-02-15 21:09:36