One important concept in testing is "code coverage". The technique is to (conceptually) place a unique print statement in every branch of every "IF" statement or loop (every basic block), and then try to write tests until you've triggered all of the print statements.
EDIT: This explains the concept, and gives a minimal approach to testing (i.e., you should test more than this, but at least this). Of course, there are tools to automate this, but not for every (new) language.
What you're describing is block coverage, which is only one of many types of code coverage that exist. By itself, it is rather limited in the types of issues it can discover.
There are many other types of code coverage, including branch/conditional coverage, state coverage and so forth that provide much greater depth of coverage that developers should look into using where possible. The Wikipedia article has a good introduction: https://en.wikipedia.org/wiki/Code_coverage.
I prefer to code in my highly customized emacs configuration when not coding in something that benefits unusually from an IDE (Java). Many people do this, though they seem to be decreasing in number somewhat - seems like younger interviewees increasingly haven't even heard of vim or emacs, somehow.
I didn't say that. Be wary of people who say they fully tested your code :)
Anyway, there's another (non-waterproof) approach: try to trigger all possible paths through the code (instead of all basic blocks), but the problem is that the number of paths can increase exponentially with code size.
The code-coverage approach, in contrast, is very cost-effecitve. For example, roughly speaking, it triggers all possible exceptions that your code can throw.
It won't trigger exception handling that hasn't been written because you forgot to check a certain sort of bad input. In that case, you may still get 100% code coverage, which many will erroneously take to mean that all possible behaviors are verified.
100% code coverage can _never_ be taken, as a figure, to indicate that testing is comprehensive.
> The code-coverage approach, in contrast, is very cost-effecitve. For example, roughly speaking, it triggers all possible exceptions that your code can throw.
Well, unless you count uncaught exceptions from things your code calls.
But the goal of unit tests is exactly and specifically to test single cases such as "if a" and "if b", discretely and independently of one another. More complex cases such as "if A and B" are what integration tests are written for.
What test suites generally show as coverage is called statement coverage. This only means that all the statements have been executed at least once by the test suite.
There are other types of coverages. Like branch coverage, which is more like what you've described. And path coverage. Path coverage tests that all independent paths are executed at least once but it's tedious and memory intensive to calculate and he hence impractical.
EDIT: This explains the concept, and gives a minimal approach to testing (i.e., you should test more than this, but at least this). Of course, there are tools to automate this, but not for every (new) language.