Depending on the language:

Unit testing is easy for beginners. Unit testing done well is much less so. This results in many inexperienced people writing poor unit tests.

In my last job, I worked on a legacy C++ code base. 500K lines of thorough testing, but none was a unit test. It took 10 hours to run the full test suite. I set about the task of converting portions for unit testability.

I was surprised how much of a challenge it was (and I learned a lot). There were few resources on proper unit testing in C++. Mostly learned from Stack Overflow. Lessons learned:

1. If your code did not have unit tests, then likely your code will need a lot of rearchitecting just to enable a single unit test. The good news is that the rearchitecting made the code better.

As a corollary: You'll never know how much coupling there is in your code until you write unit tests for it. Our code looked fairly good, but when I wanted to test one part, I found too many dependencies I needed to bring in just to test it. In the end, to test one part, I was involving code from all over the code base. Not good. Most of my effort was writing interface classes to separate the parts so that I could unit test them.

2. For C++, this means your code will look very enterprisey. For once, this was a good thing.

3. Mocking is an art. There's no ideal rule/guideline for it. Overmock and you are just encoding bugs into your tests. Undermock and you are testing too many things at once.

4. For the love of God, don't do a 1:1 mapping between functions/methods and tests. It's OK if your test involves more than one method. Even Robert Martin (Uncle Bob) says so. I know I go against much dogma, but make your unit tests test features, not functions.

5. If your unit tests keep breaking due to trivial refactors, then architect your unit tests to be less sensitive to refactors.

6. For classes, don't test private methods directly. Test them through your public interface. If you cannot reach some code in a private method via the public interface, throw the code away!

7. Perhaps the most important: Assume a hostile management (which is what I had). For every code change you make so that you can write a unit test, can you justify that code change assuming your project never will have unit tests? There are multiple ways to write unit tests - many of them are bad. This guideline will keep you from taking convenient shortcuts.

This advice is all about unit tests, and not TDD. With TDD, it is not hard to test yourself into a corner where you then throw everything away and restart. If you insist on TDD, then at least follow Uncle Bob's heuristic. For your function/feature, think of the most complicated result/boundary input, and make that your first unit test. This way you're less likely to develop the function into the wrong corner.

When I completed my proof of work, the team rejected it. The feedback was it required too much skill for some of the people in the team (40+ developers across 4 sites), and the likelihood that all of them will get it was miniscule. And too much of the code would need to change to add unit tests.

I later read this book:

https://www.amazon.com/Art-Unit-Testing-examples/dp/16172908...

And it contains quite a bit of what I learned from unit testing.