Closed WJSchakel closed 1 year ago
I can see that testSucceed
indeed does not show the correct behavior. The simplest case:
Double dn = null;
Double d1 = Double.valueOf(1.0);
Try.testSucceed(() -> d1.toString());
Try.testSucceed(() -> dn.toString());
does not throw any errors or exceptions, and of course one of the cases should throw an exception (the dn one)...
On the other hand, testFail
does seem to show the correct behavior. See the following code:
Double dn = null;
Double d1 = Double.valueOf(1.0);
Try.testFail(() -> dn.toString());
try
{
Try.testFail(() -> d1.toString());
System.err.println("NOT ok");
}
catch (Throwable e)
{
System.out.println("ok");
}
This prints ok
. I tested with Try.testFail(() -> dn.toString());
whether the null pointer assignment indeed throws an exception, and it does. I tested with Try.testFail(() -> d1.toString());
whether it throws and exception, and it does not, so the testFail throws an AssertionError
, which is to be expected -- the assertion was that the method call would throw an exception, and it didn't, so it should throw an AssertionError
-- the same error that the assert
(or assertTrue()
, assertEquals()
) throws on the failure of an assertion.
testFail
shows the wrong behavior if you use the version with 3 arguments, where you explicitly give the exception type that an assignment/execution should fail on. If another exception pops up, the test neither failed nor succeeded, but is improperly defined. I think it should throw an exception to indicate to the programmer that the test is not correctly defined.
By the way, I never understood the necessity of the testSucceed(() -> someExecution())
method for unit tests. When you expect something to succeed, just put the execution()
in the unit test, and when it does not succeed because of an error or exception, the unit test fails, as it should.
The use case for testFail(() -> someExecution())
, on the other hand, is very compelling. Instead of writing:
try
{
someExecution();
fail("this execution should have thrown an exception");
}
catch (Exception e)
{
// expected
}
we can write the one-liner:
testFail(() -> someExecution(), "this execution should have thrown an exception");
So, what is the use-case for testSucceed? The javadoc doesn't give a clue. Actually, now that I look at it, the javadoc does not give any clue about any of the method parameters for testFail
and testSucceed
... Another opportunity for improvement!
testFail
shows the wrong behavior if you use the version with 3 arguments
Ok -- clear! I will check that and make improvements. The expected behavior for testFail
in that case is that when it fails with the CORRECT exception, testFail
succeeds, and when it either DOES NOT fail, or fails with the WRONG EXCEPTION, testFail
should throw an AssertionError
. Did I understood correctly?
By the way, I will also raise this bug in djunits. djunits has a copy of the Try
and Throw
classes, to not make djunits dependent on external packages.
The expected behavior for
testFail
in that case is that when it fails with the CORRECT exception,testFail
succeeds, and when it either DOES NOT fail, or fails with the WRONG EXCEPTION,testFail should throw an
AssertionError`. Did I understood correctly?
Almost:
throw AssertionError
return null
for an assignment, do nothing for an executionthrow RuntimeException
By the way, I never understood the necessity of the
testSucceed(() -> someExecution())
method for unit tests. When you expect something to succeed, just put theexecution()
in the unit test, and when it does not succeed because of an error or exception, the unit test fails, as it should.
True. The only reason I imagine is that if it fails you get an AssertionError
as a proper test result, rather than some random exception. I think tests should fail with an AssertionError
, while other exceptions indicate that some code in the test itself is wrong. But I'm not sure about this, and the testSucceed
methods are confusing, and I can find only 1 usage.
- A wrong exception pops up: test itself is improper throw
RuntimeException
This one I don't understand. It should, in my opinion, also throw an AssertionError
, but maybe with a more extensive explanation. Note that an Error
is more severe than an Exception
, so even when there is a try...catch(Exception e)
around the testFail()
code, the AssertionError
will still make the unit test fail (as it should).
Would it be an idea to precede or append the error message (as an AssertionError
) with a string "unexpected exception thrown: XxxException instead of YyyException" instead of throwing a RuntimeException
?
And we could even precede or append the error message when the assignment or execution did not fail with a string "Assignment of execution unexpectedly did not fail with YyyException".
The only reason I imagine is that if it fails you get an
AssertionError
as a proper test result, rather than some random exception.
I disagree. When you expect a method call or assignment to succeed in your unit test, ANY deviation from that should throw an AssertionError
in the end. Period. When you just execute the code as someMethod()
and it throws any exception, JUnit will turn that into an AssertionError
and fail the unit test. The explanation of the unit test failure provides you with the stack trace, which contains all the information you need. The RuntimeError
just adds a layer of obfuscation IMHO.
Another reason for this is that the code of Try.testFail()
or Try.testSucceed()
could be wrong itself. THAT would never throw an AssertionError
, but another type of exception, and therefore is a giveaway that we should look into the code in the Try
class to resolve that unexpected exception...
The more I think about it, the more I am in favor of scrapping the testSucceed()
, by the way...
Perhaps in the end an AssertionError
results, but in the JUnit pane you have 3 results for a test:
To me the latter two categories mean:
But I realize both may happen due to faulty code either in the test code, or tested code. So an AssertionError
with additional description is fine.
And in that case indeed the testSucceed
methods make no sense. Let's remove them as they are confusing.
Agree.
Try
class should be updated. Without using Try
itself, so in the old fashioned way. Apparently we have not been complete in our tests, since especially the 2-line incorrect testSucceed()
code should have been caught... Javadoc has been extensively updated. It now states, e.g.:
<V, T extends Throwable> V org.djutils.exceptions.Try.testFail(Assignment
Method for unit tests to test if an expected exception is thrown on an assignment. This method provides an explanation message, and it is checking for a specific type of exception to be thrown. The testFail() method throws an AssertionError when the assignment does not throw an exception, or when it throws a different exception than expectedThrowableClass. A way to use the method is, for instance:
Try.testFail(() -> methodFailsOnNull(null), "call should have thrown an NPE", NullPointerException.class);
or
Try.testFail(new Try.Assignment<Double>()
{
@Override
public Double assign() throws Throwable
{
return methodFailsOnNull(null);
}
}, "call should have thrown an NPE", NullPointerException.class);
Type Parameters:
<V> value type, which is the return type of the assignment
<T> throwable type, which ensures that we provide a throwable class as the argument
Parameters:
assignment Assignment
Unit tests for testFail()
have been severely extended. There are now around 800 lines of test code for testFail()
.
testSucceed()
has been removed from the code and from the unit tests. Issue solved.
V Try.testSucceed(Assignment<V>, String, Class<T extends Throwable>)
is used to check whether an assignment runs. For example, we have a constructor that may throw aNullPointerException
. We can check thatnew ClassName("argument");
indeed runs. The method accepts a throwable class. As the constructor can throw aNullPointerException
, we give that class. Should the assignment throw e.g. anIllegalArgumentException
, the test should neither succeed or fail, but throw an exception. The test is then improper.The code reads:
Problem 1 The assignment should run successfully for the test. If the test gets in to the catch, the test has either failed or behaved unexpectedly. If, however, the thrown cause is of an expected type, we just have
value = null;
and the test succeeds. Instead, it shouldthrow new AssertionError(message);
(note that message is not even used in the method).Problem 2 In the else-block, a normal exception should be thrown, rather than an
AssertionError
, as the test behaved unexpectedly with a wrong type of cause.There is a similar method without a return type, and where the first argument is an execution:
void Try.testSucceed(Execution, String, Class<T extends Throwable>)
. This method also suffers from problem 2.Both methods also have a
testFail(...)
counterpart, which also suffer from problem 2. In all 4 cases where the message containsfailed on unexpected Throwable
,AssertionError
can simply be replaced withRuntimeException
.