unit_test
The file builtins/unit_test.e (an autoinclude) implements a simple unit testing framework for Phix.
Unit testing should be a vital weapon in any half-decent programmer’s arsenal, and can be key to a fail fast approach.
It should be just as easy to write a (permanent) unit test as it is to perform that test (once) manually.
The theory is simply that if all the components of a system are working correctly, then there is a much higher probability that a system using those components can be made to work correctly. I might also add that debugging a component in isolation is much easier than leaving it until everything else is ready.
Unit tests not only completely eliminate an otherwise extremely tedious phase of the release cycle, but also grant the confidence to make changes that could instead be just far too frightening to even contemplate. I can tell you with absolute certainty and utter seriousness that the phix compiler simply could not have been written without the help of unit testing, full stop.
While they do not actually use these routines, a quick scan of any of the sixty-odd tests\tnn***.exw files (which predate this) will reveal plenty of opportunities for using test_equal() and friends.
If I have to spend five or twenty-five minutes crafting the perfect test, and then never have to worry about it ever again, for me that sounds like an absolute bargain and a long-term timesaver that will repay that effort many times over in the months and years to come.
These routines are fully supported by pwa/p2js, except as noted below there is no way to write a logfile and no way to pause JavaScript execution, not that it should be particularly difficult to restructure some (console-based) code into "before" and "after" routines, with some (GUI-based) means of kicking off the "after" part, probably however needing some new and as yet unwritted "get_test_results" routine to explicitly enable/disable some buttons and hide/show some messages. The (un-paused) message displays do however all work just fine, and any program that uses these routines with 100% [hidden] success rates probably won’t need any modification at all.
If all goes well, no output is shown, and the program carries on normally. You can easily force [summary/verbose] output, crash/prompt on fail, etc.
Note that I have used many of the same routine names as Euphoria, but the parameters are all different [esp their order] and therefore they are not compatibile...
In particular you have to give every test a name in Euphoria, whereas here such things are optional. Also, Euphoria works by putting tests in files named "t_*" and running eutest, whereas here they are part of the app, and will start failing on live systems (eg) if not properly installed.
The very best tests are those that promise (as best anything can) some customer will not re-experience the (apparently) exact same problem this time next week/month - every (non-trivial) maintenance task should result in a new unit test, whether that uses unit_test.e or is simply a crash() (/assert) statement. [Of course unit_test.e is intended to alert you about a problem without unnecessarily hindering development, whereas a crash() can often stop it dead in its tracks.]
Nearly as good are tests that actively assist in the development process by preventing the re-emergence of bugs that just bit you.
Utterly useless tests are the ones that can never trigger, and the big problem with TDD/BDD is they are placed front and centre, and worse should they in any way actively deter writing tests that utilise knowledge learnt during implementation.
Where TDD/BDD shines is when you know up-front that nothing will be learnt and there will be absolutely no innovation, which is the very definition of soul-destroying. The same applies to some forms of OOP that insist on the creation of entire classes so banal and trivial that TDD/BDD suffices. One good thing about TDD is that it deters the use of toolchains with primitive run-time diagnostics, ie C++, C, Assembler, vs. everything from Java and Python and of course Phix, which at least try to be helpful to the developer.
Unit testing should be a vital weapon in any half-decent programmer’s arsenal, and can be key to a fail fast approach.
It should be just as easy to write a (permanent) unit test as it is to perform that test (once) manually.
The theory is simply that if all the components of a system are working correctly, then there is a much higher probability that a system using those components can be made to work correctly. I might also add that debugging a component in isolation is much easier than leaving it until everything else is ready.
Unit tests not only completely eliminate an otherwise extremely tedious phase of the release cycle, but also grant the confidence to make changes that could instead be just far too frightening to even contemplate. I can tell you with absolute certainty and utter seriousness that the phix compiler simply could not have been written without the help of unit testing, full stop.
While they do not actually use these routines, a quick scan of any of the sixty-odd tests\tnn***.exw files (which predate this) will reveal plenty of opportunities for using test_equal() and friends.
If I have to spend five or twenty-five minutes crafting the perfect test, and then never have to worry about it ever again, for me that sounds like an absolute bargain and a long-term timesaver that will repay that effort many times over in the months and years to come.
These routines are fully supported by pwa/p2js, except as noted below there is no way to write a logfile and no way to pause JavaScript execution, not that it should be particularly difficult to restructure some (console-based) code into "before" and "after" routines, with some (GUI-based) means of kicking off the "after" part, probably however needing some new and as yet unwritted "get_test_results" routine to explicitly enable/disable some buttons and hide/show some messages. The (un-paused) message displays do however all work just fine, and any program that uses these routines with 100% [hidden] success rates probably won’t need any modification at all.
Example:
test_equal(2+2,4,"2+2 is not 4 !!!!") test_summary()
If all goes well, no output is shown, and the program carries on normally. You can easily force [summary/verbose] output, crash/prompt on fail, etc.
Note that I have used many of the same routine names as Euphoria, but the parameters are all different [esp their order] and therefore they are not compatibile...
In particular you have to give every test a name in Euphoria, whereas here such things are optional. Also, Euphoria works by putting tests in files named "t_*" and running eutest, whereas here they are part of the app, and will start failing on live systems (eg) if not properly installed.
constants
The following constants are automatically defined (in psym.e/syminit). TEST_QUIET is shown twice in italics to indicate it is part of all three sets, but obviously it is only actually defined once. The three sets correspond, in order, to the first three functions documented below them:|
| = 0 -- (summary only when fail) |
| TEST_SUMMARY | = 1 -- (summary only [/always]) |
|
| = 2 -- (summary + failed tests) |
| TEST_SHOW_ALL |
= 3 -- (summary + all tests)
|
| TEST_ABORT | = 1 -- (abort on failure, at summary) |
| TEST_QUIET | = 0 -- (carry on despite failure) |
| TEST_CRASH |
= -1 -- (crash on failure, immediately)
|
| TEST_PAUSE | = 1 -- (always pause) |
| TEST_QUIET | = 0 -- (never pause) |
| TEST_PAUSE_FAIL | = -1 -- (pause on failure) |
routines
Apart from test_summary(), these are all optional.| procedure |
|
| procedure |
|
| procedure |
|
| procedure |
|
| procedure |
|
| procedure |
|
| procedure |
|
| procedure |
|
| procedure |
|
| procedure |
|
| procedure |
|
| procedure |
|
Regarding BDD, TDD, etc.
Tests are brilliant, they really are, and inadequate tests are, well, just inadequate. TDD/BDD both risk approaching the problem from 100% the wrong angle, not that I have any objection to writing maybe 10% or even 20% of tests up-front.The very best tests are those that promise (as best anything can) some customer will not re-experience the (apparently) exact same problem this time next week/month - every (non-trivial) maintenance task should result in a new unit test, whether that uses unit_test.e or is simply a crash() (/assert) statement. [Of course unit_test.e is intended to alert you about a problem without unnecessarily hindering development, whereas a crash() can often stop it dead in its tracks.]
Nearly as good are tests that actively assist in the development process by preventing the re-emergence of bugs that just bit you.
Utterly useless tests are the ones that can never trigger, and the big problem with TDD/BDD is they are placed front and centre, and worse should they in any way actively deter writing tests that utilise knowledge learnt during implementation.
Where TDD/BDD shines is when you know up-front that nothing will be learnt and there will be absolutely no innovation, which is the very definition of soul-destroying. The same applies to some forms of OOP that insist on the creation of entire classes so banal and trivial that TDD/BDD suffices. One good thing about TDD is that it deters the use of toolchains with primitive run-time diagnostics, ie C++, C, Assembler, vs. everything from Java and Python and of course Phix, which at least try to be helpful to the developer.