When I was programming in C#, using Visual Studio, NUnit, and NCrunch, we wrote TDD style unit tests. Not only did we have pretty good unit test coverage (about 70%), writing the tests was actually fun. Yes: fun. (Now only if I could put that in a 96 point font, with the blink tag.)
I attribute the fun factor entirely to NCrunch: it is pure magic. When writing TDD style unit tests becomes fun, unit tests get written.
These days I'm programming in C++. By-and-large, we don't have unit tests. We do have a few, but not many, using Google Test. Writing unit tests in C++ is not fun. There's nothing equivalent to NCrunch for C++. Google Test or alternatives like Boost Test Library are awkward to use, even though they are well thought out... they're still a bolt-on to the language and rely heavily on a lot of macro-magic to get the job done. But doing TDD style "write the test, run-and-fail the test, write the code, run-and-pass the test, refactor the code, run-and-pass the test, check-in" cycle doesn't work when the compile-then-run-tests takes 20 minutes. :sad panda: (C++20 will have contracts, which will help a lot. Funky syntax, though.)
One of my favorite languages is D. Two neat features of D is that it has contract support built into it, and unit testing built into it. Between those two features, I found writing the precondition contracts, postcondition contracts, and invariant contracts to be fun, and writing the unit tests to also be fun. Having contract support means there are a lot of simple sanity unit tests that do not need to be written, so the quantity of unit tests is a lot smaller. The syntax is very straightforward, and since it is part of the core language one doesn't have to get the team to agree upon some bolt-on unit testing framework.
For me at least, the upshot is: some languages that are much more amenable to TDD style unit testing. Other languages do not lend themselves to TDD style unit testing.
Unit tests (as a residual value) ensure basic correctness, especially for procedural and object-oriented programming wherein ensuring basic correctness is otherwise difficult. They are no substitute for automated integration tests and automated system tests. And, vice versa, integration and system tests cannot be used as a substitute for unit tests. Different domains, by different people, for different purposes. Unit tests are written by developers primarily as a design tool to aid development, with residual value as a regression suite to ensure basic correctness. (Regardless of TDD style unit tests, projects still need architecture. Unit tests are design-in-the-small.) Integration tests and system tests are written by quality engineers primarily to ensure the separate parts work together as expected.
PS: when I say "unit test" I mean that in the TDD sense. To me, things that are not TDD style unit tests are not unit tests, but the term "unit tests" is commonly used in the industry to refer to things that I would categorize as integration tests, or system tests, or performance tests, or security tests, or smoke tests, or usability tests, or acceptance tests, or bug regression tests (which are a usability tests variant). Best to be aware of the possible miscommunication due to same terminology with different semantics.
While I have given up on TDD, I have to say that with the Catch family of unittesting frameworks (currently I am using doctest) C++ has the nicest unittest experience for me. To the point that I do miss doctest when programming in python.
I've looked at Catch before. I like it, nice to see that Catch2 is still an ongoing endeavor. It is straightforward and easy to jump in and start using right away.
The single-header-file approach helps to lower the barrier to entry.
(I wouldn't use its BDD style syntax for unit tests. But that's just me, and it's a take-it-or-leave-it, so easy enough to opt-out.)
We're a place where coders share, stay up-to-date and grow their careers.
We strive for transparency and don't collect excess data.