As hobby projects, I mostly make small-ish libraries or tools, so integration tests are still small in scope. I usually write some unit tests along with the code, sometimes before sometimes after. Then I try to add a test for most bugfixes to prevent future regression.
It works pretty well. I introduce a lot of types (in Rust) to know that e.g. I'm not mixing up x and y coordinates, or comparing a length to an area. And I add unit tests for non-trivial operations to know that some edge cases work. It finds a lot of logic bugs. Often the overall program works immediately or nearly immediately if it passes all the tests.
For work, I've worked with unit tests, unit tests but with database, and a little with integration tests using Selenium, but overall test coverage was low.
Just last week I found out about mutation testing and I think that's fascinating. Introduce random mutations in the code to see whether tests catch that, as a measure of test coverage! Might be a good test of type safety as well.