For testing, writing assertions seems simple: we only need to compare results to expectations, usually using the assertion method, such as the Asserttrue () or Assertequals () method provided by the test framework. However, for more complex test scenarios, the assertion validation results with these bases can be quite awkward.
The main problem with using these base assertions is that the underlying details mask the test itself, which we do not want to see. In my opinion, we should try to make these tests use the business language to speak.
In this article, I'll show you how to use the Matcher library to implement custom assertions to improve the readability and maintainability of your test code.
To facilitate the demo, let's assume that there is a task: Let's imagine that we need to develop a class for the report module of the application system, enter two dates (start and end dates), and this class will give you all the hourly intervals between the two dates. These intervals are then used to query the required data from the database and are presented to the end user in an intuitive graphical manner.
Standard method
We first use the "standard" method to write assertions. Let's take junit for example, and of course you can use testng. We will use an assertion method such as Asserttrue (), Assertnotnull (), or assertsame ().
One of the test methods for the Hourrangetest class is shown below. It's very simple. First call the GetRanges () method to get all the hourly ranges between two dates. Then verify that the range returned is correct.
private final static SimpleDateFormat SDF = new SimpleDateFormat ("Yyyy-mm-dd hh:mm"); @Test public void Shouldreturnhourlyranges () throws ParseException {//given Date Datefrom = Sdf.parse ("
2012-07-23 12:00 ");
Date Dateto = Sdf.parse ("2012-07-23 15:00");
When final list<range> ranges = hourlyrange.getranges (Datefrom, Dateto);
Then Assertequals (3, Ranges.size ());
Assertequals (Sdf.parse ("2012-07-23 12:00"). GetTime (), Ranges.get (0). Getstart ());
Assertequals (Sdf.parse ("2012-07-23 13:00"). GetTime (), Ranges.get (0). Getend ());
Assertequals (Sdf.parse ("2012-07-23 13:00"). GetTime (), Ranges.get (1). Getstart ());
Assertequals (Sdf.parse ("2012-07-23 14:00"). GetTime (), Ranges.get (1). Getend ());
Assertequals (Sdf.parse ("2012-07-23 14:00"). GetTime (), Ranges.get (2). Getstart ());
Assertequals (Sdf.parse ("2012-07-23 15:00"). GetTime (), Ranges.get (2). Getend ()); }
There is no doubt that this is an effective test. However, it has a serious drawback. There is a lot of duplicate code behind the//then. Obviously, they are copied and pasted code, and experience tells me that they will inevitably produce errors. Also, if we write more similar tests (and we certainly have to write more tests to validate the Hourlyrange class), the same assertion statement will be repeated in every test.
Excessive assertions and the complexity of each assertion weaken the readability of the current test. A lot of low-level noise makes it impossible to quickly and accurately understand the core scenarios of these tests. As we all know, the number of times we read the code is much larger than the number of times it was written (which I think is also true for the test code), so we naturally have to find ways to improve its readability.
Before we rewrite these tests, I want to focus on another of its drawbacks, which is related to the error message. For example, if the GetRanges () method returns one of the range that is different than expected, we'll get a message like this:
Org.junit.ComparisonFailure:
expected:1343044800000
actual:1343041200000
This information is too unclear and should be improved.
Private method
So, what can we do about it? Well, the most obvious way is to put the assertion into a private way:
private void Assertthatrangeexists (list<range> ranges, int rangenb,
string Start, String stop) throws parseexception {
assertequals (Ranges.get (RANGENB). Getstart (), Sdf.parse (start). GetTime ());
Assertequals (Ranges.get (RANGENB). Getend (), Sdf.parse (stop). GetTime ());
@Test public
void Shouldreturnhourlyranges () throws ParseException {
//given
Date Datefrom = Sdf.parse (" 2012-07-23 12:00 ");
Date Dateto = Sdf.parse ("2012-07-23 15:00");
When
final list<range> ranges = hourlyrange.getranges (Datefrom, dateto);
Then
assertequals (Ranges.size (), 3);
Assertthatrangeexists (ranges, 0, "2012-07-23 12:00", "2012-07-23 13:00");
Assertthatrangeexists (ranges, 1, "2012-07-23 13:00", "2012-07-23 14:00");
Assertthatrangeexists (ranges, 2, "2012-07-23 14:00", "2012-07-23 15:00");
Is that better? I would say yes. It's certainly a good thing to reduce the number of repetitive code and improve readability.
Another advantage of this approach is that we can now more easily improve the error message when validation fails. Because the assertion code is drawn into a method, we can improve the assertion and easily provide more readable error messages.
To better reuse these assertion methods, you can place them in the base class of the Test class.
However, I think we might be able to do better: there are drawbacks to using private methods, and as test code grows, many test methods will use these proprietary methods, and their disadvantages will be more pronounced:
It is difficult to name the assertion method clearly to reflect its checksum content.
As demand grows, these methods tend to receive more parameters to meet the requirements of more complex checks. (Assertthatrangeexists () now has 4 parameters, already too much!) )
Sometimes, in order to reuse the code in multiple tests, some complex logic is introduced into these methods (usually in the form of Boolean flags or, in some special cases, ignoring them).
In the long run, all the tests written with the private assertion method mean that there will be some problems with readability and maintainability. Let's take a look at another solution without these drawbacks.
See more highlights of this column: http://www.bianceng.cnhttp://www.bianceng.cn/Programming/Java/