DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. Avoid Corner Cases

Avoid Corner Cases

Jens Schauder user avatar by
Jens Schauder
·
Oct. 17, 12 · Interview
Like (0)
Save
Tweet
Share
7.92K Views

Join the DZone community and get the full member experience.

Join For Free

Lets assume you are tasked with testing a method with the following signature:

 public int foo(int a, int b);

If you know nothing else about the method, what would you use for test values? Here are the values I would choose:

0, 1, -1, 4711, -2342, Integer.MIN_VALUE, Integer.MAX_VALUE

Why? Well experience shows that these values cover cases where software is likely to fail.

Why? Because at or between these values many algorithms and formulars behave different. Take the Heaviside-function as an example. It looks like this:

If you got the implementation right for 4711 it is probably correct for 4712 and 4713 as well, but bets for 0 and -1 are completely off.

So if you reverse that thought, what does this tell you about how to design or implement your software?

I think it results in two rules:

Avoid superfluous corner cases

Sometimes the way you choose to implement affects the existence of boundaries. Take for example the Standard Code Retreat programming problem of Conways Game Of Life. Follow the link if you don’t know it, I’ll wait.

Welcome back. When people try to implement it for the first time, about half of them use an array (or list of lists) for representing each cell. This forces them to initialize the array. Since they don’t want to waste huge amounts of memory, they typically initialize only a limited area, let’s say 100×100 cells.

Now we got a corner case that didn’t exist in the original problem: What ever we do we have to take extra care if we do it on or next to the edge of the initialized grid.

You are probably thinking: “But my problems aren’t abstract thingies on infinite grids, so this doesn’t apply.” Sorry, you are most probably wrong. Do you loop over collections in your code? If you do you have a new boundary condition: an empty collection vs. a non-empty collection. These two go through two significant different code paths in code structured like this:

    <some initialization>
    for {
    <some processing>
    }
    <some cleanup>

It gets even worse when you don’t use the foreach loop in the language of your choice, but iterate over an index:

It introduces the following new boundaries: 1 vs many elements (maybe you used 0 instead of i as an index). The first vs the last vs an element in the middle.

Compare this mess with something like

    someCollection.map(somefunction(_))

It doesn’t have any of the boundaries.

Another example: Have you ever returned a mutable object from a method to which the owning object still had a reference? Voila, you just created another corner case: Read-Only access from the client vs. mutating access of the client.

If you have mutable state accessed by multiple threads you have probably an almost infinite number of corener cases: Every ordering on byte code level (at least) of accesses from the different threads. That’s basically why concurrency is so hard.

On a more abstract level, some problems look like this:

and you can choose your solution to look like this:

or like this:

Which on is easier to check for completeness? Which is probable to need more code?

Make the necessary boundaries easy to test

Often you don’t have a choice: there is a hard boundary in the requirements. Like “if the value of the transaction is larger then 1000Euro, display a warning.” or “If there is unsaved data, display a * behind the title.”

In these cases make sure the corresponding decision is made exactly once in your code. That piece of code should probably do nothing but that distinction. Make sure you unit test it well.

unit test

Published at DZone with permission of Jens Schauder, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • What Is a Kubernetes CI/CD Pipeline?
  • Express Hibernate Queries as Type-Safe Java Streams
  • The Future of Cloud Engineering Evolves
  • How to Create a Real-Time Scalable Streaming App Using Apache NiFi, Apache Pulsar, and Apache Flink SQL

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: