There is often a question as to whether a test such as a business rule should be made more generic. In this article, we’ll examine ways of making tests more generic.
A Generic Business Rule
For the ticket ordering system (see xxx), there was a business rule regarding the discount to give for large orders. An acceptance test for the discount percentage to apply might look like the following:
| Number Seats | Discount % | Notes | 19 | 0% | No discount for 1 to 19 seats | 20 | 10% | 20 or more seats |199 | 10 | less than 200 seats | 2 | 20% | 200 or more seats
If the breakpoints where the discount was given or the percentage of the discount changed, this test would have to be changed. In many instances, the business rule is simple enough that this is not an issue. However, if the business rule uses a configuration file which can change, then that configuration file should be part of the test. Let’s suppose that there is such a file, that looks something like:
Given configuration is: | Name | Value | | SEAT_COUNT_FOR_LOW_DISCOUNT | 20 | | PERCENTAGE_FOR_LOW_DISCOUNT | 10% | | SEAT_COUNT_FOR_HIGH_DISCOUNT | 200 | | PERCENTAGE_FOR_HIGH_DISCOUNT | 20% |
Using these names, one could write the business rules as:
| Number Seats | Discount Percentage | | SEAT_COUNT_FOR_LOW_DISCOUNT -1| 0% | |SEAT_COUNT_FOR_LOW_DISCOUNT | PERCENTAGE_FOR_LOW_DISCOUNT | | SEAT_COUNT_FOR_HIGH_DISCOUNT-1| PERCENTAGE_FOR_LOW_DISCOUNT | | SEAT_COUNT_FOR_HIGH_DISCOUNT | PERCENTAGE_FOR_HIGH_DISCOUNT|
The business rule should use whatever terms the customer uses or whatever the triad agrees upon.
In the test implementation underneath, the configuration information is read into a common map. When the input for Number Seats and Discount Percentage is read in, the corresponding names are looked up in that map. If there is an arithmetic necessary (such as the -1), that is applied.
As an alternative, the arithmetic can be performed in the map itself, so the individual input variables only do lookup and no arithmetic. For example:
Given configuration is: | Name | Value | | SEAT_COUNT_FOR_LOW_DISCOUNT | 20 | | PERCENTAGE_FOR_LOW_DISCOUNT | 10% | | SEAT_COUNT_FOR_HIGH_DISCOUNT | 200 | | PERCENTAGE_FOR_HIGH_DISCOUNT | 20% | When derived values are created Then they are computed as | Name | Value | | SEAT_COUNT_FOR_LOW_DISCOUNT_LT | SEAT_COUNT_FOR_LOW_DISCOUNT-1 | | SEAT_COUNT_FOR_HIGH_DISCOUNT_LT| SEAT_COUNT_FOR_HIGH_DISCOUNT– 1| And the business rule test looks like | Number Seats | Discount Percentage | | SEAT_COUNT_FOR_LOW_DISCOUNT_LT | 0% | | SEAT_COUNT_FOR_LOW_DISCOUNT | PERCENTAGE_FOR_LOW_DISCOUNT | | SEAT_COUNT_FOR_HIGH_DISCOUNT_LT| PERCENTAGE_FOR_LOW_DISCOUNT | | SEAT_COUNT_FOR_HIGH_DISCOUNT | PERCENTAGE_FOR_HIGH_DISCOUNT|
As an alternative using the stated values for SEAT_COUNT_FOR_LOW_DISCOUNT and so forth, the test could read the actual configuration file for those values and use them in the test. In addition, the test could become an exploratory test by assigning values to the names based on a random number generator or some set of valid values.
A Generic Date
One often has inputs or persistent data that contain date values. For example:
| Transaction ID | Date | Amount | | 111 | 1/1/2017| $200.00 |
The operation under test is dependent on the current date. You could have a test double for the date whose value is set by the test. Some teams use February 2, 1993 as the base date for all tests in honor of the movie. You’d have in the test, something like:
And today is | Date | |1/2/2017|
If you use a test double, then transactions do not have to be changed.
However, there are occasions when you do not have control over the date, so you can use a name as TODAY to represent the current date of the system. Other dates are relative to today, so you can set up something like the following
Given: | Name | Value | | Tomorrow | TODAY + 1| | Yesterday | TODAY – 1| | Next Day After | TODAY + 2|
If the transaction is from yesterday, it would look like:
|Transaction ID | Date | Amount | | 111 | Yesterday | $200.00|
In this case the Date class which is used to input the values can parse the string and substitute in the appropriate value for the date.
Using Preprocessing
Using lots of names can be confusing. Changing values in multiple places because one value changed can be extra work. There is an intermediate ground – using a preprocessor. The preprocessor takes a test file and substitutes values for names. It can also perform simple arithmetic computations. The syntax for the preprocessor is up to you. If you want to use an available one, you can check out open source implementations of the C preprocessor. The “#define NAME value” sets up the value for a substitutable name.
Here’s an example.
#define SEAT_COUNT_FOR_LOW_DISCOUNT 20 #define PERCENTAGE_FOR_LOW_DISCOUNT 10% #define SEAT_COUNT_FOR_HIGH_DISCOUNT 200 #define PERCENTAGE_FOR_HIGH_DISCOUNT 20% #define SEAT_COUNT_FOR_LOW_DISCOUNT_LT SEAT_COUNT_FOR_LOW_DISCOUNT -1 #define SEAT_COUNT_FOR_HIGH_DISCOUNT_LT SEAT_COUNT_FOR_HIGH_DISCOUNT – 1 | Number Seats | Discount Percentage | | SEAT_COUNT_FOR_LOW_DISCOUNT_LT | 0% | | SEAT_COUNT_FOR_LOW_DISCOUNT | PERCENTAGE_FOR_LOW_DISCOUNT | | SEAT_COUNT_FOR_HIGH_DISCOUNT_LT| PERCENTAGE_FOR_LOW_DISCOUNT | | SEAT_COUNT_FOR_HIGH_DISCOUNT | PERCENTAGE_FOR_HIGH_DISCOUNT |
The preprocessor turns this business rule test into:
| Number Seats | Discount Percentage | | 19 | 0% | | 20 | 10% | | 199 | 10% | | 200 | 20% |
If you have #defines that are common, then you can put them in a single file and use the preprocessor command #include “filename” to insert it into other files.