Ok, agreed. Command (action)/Query (function) separation is another rabbit-hole that I find few devs have gone down...[)roi(];19043036 said:Sure,
As we know, a function whose return value is determined solely by its input values and has no side effects; is always easy to test. Inversely actions without a return value, with hidden input / outputs and unmanaged side effects is very difficult to test.
Ok, I get what you say and agreed.[)roi(];19043036 said:Simple example is how we'd go about automating a test for "WriteLine"; because whilst it's inputs are clearly defined, it has no obvious output; and its return type is "void"; yet it affects state (stdout) & can throw exceptions. Writing tests for WriteLine, or wrapping every WriteLine in a try/catch block seems like a foolish endeavour because rarely do we care if it works or not; but most actions are certainly not the same.
Surely ascertaining whether "db.executeInsertQuery(sqlQuery, sqlParameter)" worked is important; yet its design makes it inherently difficult to test. It becomes a "set up the world" scenario, where complex pre/post test actions have to accompany a test in order to recreate the "perfect world" scenario prior to testing.
These design issues are rarely contained at methods; meaning anti-test patterns most often repeat. Test Mocks are non production plugin alternatives for testing, they certainly don't address design limitations.
Per definition, an action has a number of artifacts and testing should ensure that those artifacts are as expected; for 'successful' code paths as well as any kind of error condition. Testing the action by plugging in mocks for its external dependencies may yield different results, depending on the mock(s) used and how they behave. This makes the mocks more complex in that they need to somewhat represent real-world behaviour.
My software product has an extensive set of automated test cases where each has a Setup/Run/Verify/Teardown cycle. The Setup phase sets up the environment for that particular test to run. The Run phase executes the necessary action. The Verify phase verifies that the necessary artifacts have occurred for that scenario and setup. For success or particular error cases. The Teardown phase does any required de-init. Yes these tests are a huge code base on their own and is a huge, exhaustive investment. But it is one I cannot release without.
Dunno what the alternatives are though....