Crucial to good Object Orientation, is the separation of concerns and capsulation. This means your objects need a clear API that limits the way the internal state can be changed. The object itself must be responsible for the state and must guard its integrity. While the API defines what operations are allowed, these methods have restrictions of their own. It is essential that the parameters provided to these methods are within the limitations. The only solid way of checking these values is by using preconditions.
Preconditions do not replace tests, as test are focussed on the correctness of the method, whereas preconditions are focussed on the correctness of the parameters. Moreover, tests should verify these limitations as well to make sure no unexpected results are returned.
Similar as preconditions there exists postconditions. I am not a fan of this as in my opinion they serve the same purpose as tests. Moreover it is often impossible to have a separate check to verify the outcome of your result with respect to the input. This is exactly why you wrote the method, so doing it all again as a post condition is a bit ridiculous.
Even if you do have postconditions, what will you do if you see one is violated? Do you throw an exception? In that case the callee has to handle it, but what can he do? He did not cause it as the preconditions did pass, yet he is now responsible of dealing with it. Of course the callee would then know that he should not truest the result.
One tricky part, is whether you should use preconditions in your private methods as well. Because the method is private, you should be able to controle the parameters that are specified to this method, however I still prefer having preconditions, even when they are redundant. Finding an exception early is worth the extra effort. Moreover, it clearly states the limitations of the method in case somebody new is looking at your code.
My personal rule is that every method must have preconditions if there are limitations to the parameters. It is hard to find a situation where there is no limitation on a parameter, as a parameter can usually be
null. Every single limitation is also tested by a simple unit test.