By this time, every developer probably knows that along with a hundred other tasks, they're also supposed to write unit tests. One common question from devs still new to testing is "how many tests are enough?" I think there's a couple idealistic guidelines (disclaimer: I haven't personally implemented all the ideas below, it's more of a brain dump). My goal here isn't to say "as a developer, you need to add even more tasks to your already-full plate", but rather "here are practical ideas to help you know when you're done."
Code coverage - Perhaps the most obvious, and automated, thing is code coverage. The team may agree that X% coverage is sufficient, and then have the automated build fail when new code doesn't meet that policy.
Test Lines of Code - Some developers explain that they usually have X% lines of code for their tests as they have for their production "system-under-test" code. I've heard devs mention between 40% and 100% (one line of test code per production code). I'm personally not too sure how well this works out, and if it couldn't more effectively be captured by code coverage.
Manual "code smell" techniques:
The logic is flushed out - Unit tests are great for checking boundary conditions, different code paths, various inputs, etc... If you don't have enough tests to catch the basic logic, then you don't have enough tests.
The logic is documented - Related to the concept above, one of the benefits of unit tests is to document the code, especially the edge case conditions (what happens if this input array is null, what if I pass in a negative int, etc...). Ideally there are sufficient tests such that the common boundary cases are easily exposed and documented for someone who is reviewing the code.
Will the tests catch errors? - Ideally, there is sufficient test coverage such that a test will fail if another developer "accidentally" breaks your code.
Be able to write new tests - Ideally, there would be enough testing infrastructure such that you can write a new test for every logic error that arises. Even if a component has little code coverage. For example, often having to write even just a single unit test will force you to think about the component such that you could write more tests if you had to.