Unless you do it badly, and then it's really hard. It does take thoughtful effort, though. To test whether a particular piece of code works, you need a test harness that demonstrates that:
The correct output is generated for all valid inputs.
The appropriate failure behavior is generated for all invalid inputs.
That sounds innocuous enough, yet for all but the simplest of functions, it is just not practical to exhaustively perform this testing. The set of valid inputs is usually very large, and it's impossible to test each input individually. You'll have to pick a smaller set of representative input values. The set of invalid inputs is almost always much larger than the set of valid inputs, so you have to pick a number of ...