Software Quality Assurance: Equivalence Classes
In my previous post I discussed some of the common testing basics when developing a product. In this post I will be going over equivalence classes; that is, identifying characteristics within a feature and classifying them based on a given set of conditions.
An Equivalence class is a set of data that has equivalent characteristics. When you decide on a feature in a program, you must first gather what is important based on the domain knowledge. Equivalence classes can be combined into sets with each value in a class (yes/no answer). However, we can have a value in multiple classes. Let's take the following example, what equivalence classes can you come up with from the data?
3, 099, 127, 003, 128, +05, -3, -128, 0A1\
- Positive/Negative
- Leading Zeros/Non-Leading Zeros
- Leading Sign/No Leading Sign
- Single Digit/Non Single Digit
- Single-digit parsed value (ignore sign and leading zeros)/Not single digit value
- Numbers that can be stored in a signed byte/Not
- Decimal Number/Not a Number
In a rectangle, there are the four edges, the corners, and all the area inside. Similar to test scenarios, prime candidates for testing a point would be on the edge or the corners. With a rectangle we may have the equivalence classes: Left-Boundary, Right-Boundary, Top-Boundary, Bottom-Boundary, Top-Left-Corner... etc.
We can see that there are far more useful scenarios for testing these boundaries than testing the middle of the range. This is simply because we are more likely to run into defects and bugs while testing the boundaries of equivalence classes than the middle. Now let's try this with an exercise (a common test scenario by Myers in 1969).
- Triangle problem
- Program to identify triangles given three numbers
- Scalene, Equilateral, Isosceles, Invalid
- Exercise:
- Identify input classes for individual values (ensure that each value is valid before moving on in the steps)
- Identify equivalence classes and sets of equivalence classes for 3-valued sets of individual numbers that fit
- Use representative values to identify power tests sufficient to test all of your equivalence classes.
- Ensure that you cover the boundary, borderline, and mid-range of each value of each equivalence class.
public enum TriangleType
{
// The three do not form a triangle
Invalid,
// The three sides form an equilateral triangle
Equilateral,
// The three sides form a right isosceles triangle
RightIsosceles,
// The three sides form an isosceles triangle
Isosceles,
// The tree sides form a right scalene triangle
RightScalene,
// The three sides form a scalene triangle
Scalene,
}
The above enumeration TriangleType will give us the possible equivalence classes. We obviously know the four types Isosceles, Scalene, Equilateral and Invalid. But there is also the different types of Scalene and Isosceles. Take care to include these in your equivalence classes.After we have specified all the different types of equivalence classes we can now construct our Triangle structure.
public struct Triangle
{
private TriangleType myType;
private double myLongestSide;
private double myLeg1;
private double myLeg2;
private const double Tolerance = 1E-09;
.........
I have taken care to add the TriangleType and the different sides. When you construct a Triangle you will want to have the three sides of course set. I also have set a Tolerance level which I will discuss later in the initialization part.
public Triangle(double side1, double side2, double side3)
{
myType = TriangleType.Invalid;
myLongestSide = myLeg1 = myLeg2 = 0;
if (side1 > 0 && side2 > 0 && side3 > 0)
{
if (side1 < side2)
{
if (side2 < side3)
{
Initialize(side3, side1, side2);
}
else
{
Initialize(side2, side1, side3);
}
}
else if (side1 < side3)
{
Initialize(side3, side1, side2);
}
else
{
Initialize(side1, side2, side3);
}
}
}
When we actually create the triangle we need to analyze different equivalence classes based on characteristics that we measure. The common approach will be to determine what values or characteristics define a given equivalence class and even still what other cases will default a case.
I'll make the note that our Initialize method is taking in the following parameters:
private void Initialize(double longestSide, double leg1, double leg2)
The first parameter is the longest side on the triangle, with the other two legs passed in after.
If we look at the first condition: if (side1 > 0 && side2 > 0 && side3 > 0)
We will notice that it the condition is true, then the equivalence class is Invalid. Otherwise we'll move on the other conditions.
In the next condition:
if (side1 < side2) there's one of two scenarios that we can look at for initializing (if it is true).- side1 is smaller than side2
- Is side2 smaller than side3?
- Side2 is the largest side
- Initialize(side2, side3, side1)
- Side3 is the largest side
- Initialize(side3, side2, side1)
- Is side1 smaller than side3?
- Initialize(side3, side1, side2)
- Otherwise
- Initialize(side1, side2, side3)
if (((leg1 + leg2) - longestSide) > Tolerance)
{
TriangleType type = TriangleType.Scalene;
myLongestSide = longestSide;
double scaledLeg1 = leg1 / longestSide;
double scaledLeg2 = leg2 / longestSide;
if (Math.Abs(scaledLeg1 - scaledLeg2) < Tolerance)
{
leg1 = leg2;
scaledLeg1 = scaledLeg2;
type = TriangleType.Isosceles;
if (Math.Abs(1d - scaledLeg1) < Tolerance)
{
type = TriangleType.Equilateral;
}
else if (Math.Abs(scaledLeg1 * scaledLeg1 - .5d) < Tolerance)
{
type = TriangleType.RightIsosceles;
}
}
else if (Math.Abs(1d - scaledLeg1) < Tolerance || Math.Abs(1d - scaledLeg2) < Tolerance)
{
// Note: no need to check for RightIsosceles if the longest side is one of
// the sides with equal length
type = TriangleType.Isosceles;
}
else if (Math.Abs((scaledLeg1 * scaledLeg1 + scaledLeg2 * scaledLeg2) - 1d) < Tolerance)
{
type = TriangleType.RightScalene;
}
myType = type;
myLeg1 = leg1;
myLeg2 = leg2;
}
In our first condition we use the old is it a triangle formula. Side A + Side B - Longest Side Greater Than 0 or at least the tolerance in this case. If it is then we can start with a Scalene type.
Scalene:



double scaledLeg1 = leg1 / longestSide;
double scaledLeg2 = leg2 / longestSide;
if (Math.Abs(scaledLeg1 - scaledLeg2) < Tolerance)
We can determine if the triangle is an equilateral if the 1d - scaledLeg1 is less than our Tolerance level. Otherwise, if the scaledLeg1 squared - .5d is less than our Tolerance level then it's a Right Isosceles triangle.
if (Math.Abs(1d - scaledLeg1) < Tolerance)
{
type = TriangleType.Equilateral;
}
else if (Math.Abs(scaledLeg1 * scaledLeg1 - .5d) < Tolerance)
{
type = TriangleType.RightIsosceles;
}
We know that it's an Isosceles if two sides equal each other so the next condition handles as opposed to the first condition.
else if (Math.Abs(1d - scaledLeg1) < Tolerance || Math.Abs(1d - scaledLeg2) < Tolerance)
Other than this, if all three sides are not equal, then it's a Scalene triangle, handled by the next condition.
else if (Math.Abs((scaledLeg1 * scaledLeg1 + scaledLeg2 * scaledLeg2) - 1d) < Tolerance)
To sum this all up we will simply test a few cases, I won't go into detail about utilizing our testing principles since this tutorial is for learning the concept of equivalence classes but you get the idea. However, I hope this has helped you understand some of the basics to identifying characteristics into classes. In the next post I'll discuss how to properly identify features.