Have you ever been certain about a particular restaurant’s opening hours? You’ve been there a million times. Then one holiday you're hungry and want to eat there, but you suddenly realize that you don't know if they're open or not! 😦 What do you do? You ask Google!
Writing a test for situations which are outside of the ordinary is a little like asking the great oracle of Google. You get to define and prove how your software would respond to those irregular situations. By doing so, you get to avoid the pain of being surprised by unexpected failures in real life! Remember the Ariane rocket unexpectedly turning to its engineers yelling, “surprise!” in the first chapter? 😛
Up until now, we’ve only covered the more intuitive and obvious sad paths like trying to add a null to a number. You want to have great unit tests, right? This involves considering a range of unusual situations in which your app might be used and testing for them. How do you come up with these? Fortunately, testers before us have thought about this, and there are several different types of unexpected scenarios you should consider when writing tests. Let’s dive in!
Leverage edge cases for sad paths
Edge cases are test cases designed to handle the unexpected at the edge of your system and data limits.
What’s the edge of your system? And why are we talking about data limits?
This happens when the data you're working with pushes your code to the extremes of either your business rules or Java’s own types. Let's take each of these in turn.
Edge cases from business rules
First, consider working in a company which streamed music to people over the internet. To listen to music without the annoying interruption of commercials, your users might need to have a paid subscription for that month. What happens when a listener starts playing a two-minute song, one minute before their subscription expires? Perhaps they have a quota of how many songs they can listen to at a time; what happens if two songs are started almost simultaneously - will they listen to both? Such rules define limits of how your software is used, based on the needs of your business.
Edge cases from technical and physical limits
The biggest positive number an integer can hold is about 2,147,483,647. This special number is stored in Integer.MAX_VALUE. If you were building a calculator, what do you think would happen if you added 1 to this? 1 more than the maximum? This type of edge case is a boundary condition.
Simply adding 1 to a large number can counter-intuitively result in a very small negative number. Don't believe me? Let's prove it!
To see what Java thinks about it, use jshell. Here’s an example of what you’d see. The value on the second row after the ‘⇒’ is the answer to Integer.MAX_VALUE +1.
Wow! That is a really small negative number! I only added one! How should your calculator deal with this? There's a new question to ask your product owner!
When you’re having fun with puzzles, the last thing you want is for this situation to stump your users. You might want to avoid this sort of error by throwing an exception if either argument is more than half of MAX_VALUE. Or, you might choose to use different data type, such as a long. Before deciding on what to do, you need to recognize that the edge case exists.
How can I find good edge cases to test for?
Good questions to ask when looking for edge cases are:
What would be a wacky value to pass to this method, which is still legally allowed by its defined type? E.g., Passing a null, empty or insanely long string to Interger.parseInt(String)
Will my app be working in a few years? Do my business rules allow for increasing volumes of data (e.g., larger collections)? Do I have counters or strings which will grow to present problems later?
If writing a method, ask the question, "What could I try to do to break it?" Does it take any arguments which I could try to pass with unusual values? Such as nulls, incorrectly configured objects, or empty strings, for instance.
Do I need to code with some level of precision for my test cases (e.g., two decimal place accuracy)? How should my code behave if it has to round several times? For example, divide by two and round;
@Test
divide by two and round again. Would it still be accurate enough?
To help you get your head around these, let's work through an example:
When you start testing your edge cases, remember that if it fails and there’s no solution, figure out a graceful way to deal with the failure. Always consult with your team and product owner. You don’t get to make up what happens by yourself. 🙂
Use corner cases for your sad paths
Have you ever had a really bad day? One of those where everything seems to be going against you? Your train might have been delayed, or perhaps you were stuck in traffic? 🚧 That morning cup of coffee might have fallen out of your hands. ☕️ Even the internet is against you! 📡
Your software has to run in that same real world, which is filled with imperfections, glitches, and best efforts. When you release your code, it will run on a computer of some sort, far away from your desktop. Unexpected things might happen. Engineers sometimes joke that the cloud is only someone else’s computer! (The providers still use a real computer to run their cloud on.) Reaching their computers will depend on global internet infrastructure for connectivity. Your code also depends indirectly on the operating system in which your JVM runs. If you think about it, there are a lot of working parts. Every now and then one will go wrong despite all your best efforts.
You can’t always predict what might go wrong, but there are questions you can ask to help test your code for situations which might affect your ability to keep commitments. Making a bad mistake can affect your company’s reputation and may even affect its revenue! Worse than that, it might affect the day of a real user out in the world.
There are so many possibilities, where do I even start? 😧
Good question! Below are some questions to ask when looking for corner cases. Remember, not all corner cases will make sense to test. You’ll want to identify some and then prioritize which ones need to be handled. Check them out:
What happens to my user’s data if the program crashes?💥 Do you leave incomplete data somewhere? Is that okay?
How might my code violate any legal responsibilities? 📃
How might my code break any business promises or agreements? 📊
How does my program handle external issues?
What if we had a random network outage?
What if the server ran out of disk space? 💾
What if there was an issue with a service we rely on?
What happens if my user does the same thing twice? 🔁
Will it behave as expected?
What should it do?
If other services I rely on (perhaps a database or Google) become slow, what happens to the rest of my software? ⏱
There are many more questions you’ll ask depending on your application. There's no hard and fast rule for coming up with corner cases, but brainstorming different situations your method will encounter is one way to help you come up with those questions.
Let's recap!
Edge cases are test cases designed to gracefully handle the unexpected at the edge of your system and data limits.
Corner cases test unlikely situations which your application might find itself in.
Understand your business rules and technical limits to help brainstorm questions to guide you in defining those less likely test cases.