This answer is a frame challenge, because I feel like you're trying to have your cake and eat it too.
There are two ways people write tests in the real world: reasonably, and pedantically. These are just names I picked, they are not meant to imply one is better than the other, but they are very different beasts.
Reasonable testing strategies rely on only writing tests for reasonably expected situations, and writing tests ad hoc for any additional situations that are concretely encountered. The main goal here is to prevent time wastage due to pedantically testing every possible situation, even ones that are theoretically possible but never actually happen.
The pro is that you keep your testing effort down to what's relevant, the con is that you're more likely to find a bug, realize that a test could've uncovered this sooner, and now write a test for it.
Pedantic testing strategies, on the other hand, prefer that time wastage in writing every possible test, because they really want to avoid having to iteratively improve their testing suite. This is much preferred in cases where software can't easily be patched (e.g. embedded systems) or where mistakes can cost lives or serious bodily harm (e.g. medical equipment, safety systems, ...).
The pro is that you know your code inside out, the con is that is takes multiple times longer to test your software, and your codebase (and indirectly your dev team spirit) becomes very change-unfriendly due to the impact such a change may have on your test suite (and the effort that this entails).
By "complete" I mean, something that is robust, that tests all the edge cases of the function, that so much as "proves" it to be correct.
Okay, so this puts you squarely in the pedantic testing strategy approach.
- It should also be sophisticated, and not just manual
- I also don't think it's ideal to say write one test for every possible combination of something (given we have a lot of time), that is not sophisticated enough. There should be a generic solution or automatic solution in that case.
This is why I say you're trying to have your cake and eat it. You're asking for a test suite that carries all the confidence of having done the complete legwork, without having done all of the actual leg work.
It is also impossible to at the same time test/confirm every edge case, while not testing every input. The simple example is as follows:
public string MySpecialMethod(int input)
I need you to test this method. But, by your own standard, you should both make sure that there is no unexpected behavior for any int value that is passed into the method, and additionally you are not allowed to test every input value.
Big spoiler: I wrote some special if (input == ...) throw new Exception();
logic, and you don't know what the ...
is. Well, maybe you'll get lucky and happen to encounter the value in the limited subset of possible input values that you're going to test.
But can you really be confident if your test requires a wishful hope that you happened to encounter this issue? No. It's the opposite of confidence, or having tested all edge cases.
You may argue that in a white box testing scenario, you're allowed to see inside the logic, but you're missing the point. I used a blatant if throw
example here, but bugs are generally not blatant.
Since you are asking for complete confidence in the test output, my argument applies to any possible bug that could arise due to any number of possibilities, which can include certain combinations of input values, which in turn suggests that you can't have perfect confidence without having tried those combinations of input values.
What should the unit or integration tests include generally, to be complete coverage of every edge case?
This is simply not meaningfully answerable without so much as a faint grasp on what the object under test is.
If you want a general blanket answer that covers every base and gives you perfect confidence, without any concrete example, the only answer you're going to get is rigorous brute force.
To conclude, I don't know what you're expecting from having posted this question. If there was an answer here that is simultaneously easy to explain, gives you perfect confidence, is sophisticated, covers every base for every possible object under test, and doesn't cost ungodly amounts of time to write/run these tests; it would literally be the panacea to testing strategies and we would all be doing this and nothing else any more.
ForLance(int)
method for you. I need you to test it and make sure that it doesn't throw any exceptions for any int value you pass into it. Oh, and by your own ideas, you're not allowed to test every int value. It's impossible for you as a test writer to know what arbitrary int value I may have picked to throw an exception. You'd need to go looking for it. – Flater Feb 23 at 9:31