Ian Cooper implores you to test behaviors, not implementation details. His video “TDD, where did it all go wrong” expands on these points a bit. The summary is that a bunch of really thorough tests with explicit inner knowledge will actually inhibit refactoring of the code under test — a sort of safety net of strangulation.
Some instances of this problematic over-specification are subtle. (Ian gives the example of adding unit tests in response to adding a new class, rather than doing so on a new requirement or use case.) However, one red flag is particularly easy to spot: exposing internal members “just for testing.”
Take the example of a specialized data structure like a skip list. For the most part it is supposed to behave like an ordinary sorted list with methods like Add, Remove, and Find. However, it is meant to facilitate fast searches and its internal structure is relatively complex as the number of elements and layers increases. It might be tempting to fully expose the innards of the skip list so that we can TDD our way to a correct implementation. However, this test-only code aside from being a code smell will greatly hinder any attempts to improve or modify this internal structure later, even if the public API remains completely stable.
Does this mean we should just trust the implementation is correct with no deeper validation at all? Well, that’s not quite satisfying either. In other similar situations, I’ve found an appropriate compromise. Rather than expose the guts, come up with a relatively readable and stable stringification.
Using the skip list example again, we might override the
ToString() method to return something like this:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 3, 4, 6, 9], [1, 4, 6], 
With this, we can write a few simple string assertions for various sequences of adds and removes and get pretty good confidence that the internal level structure is sound without caring about whether we used an array or a list, the name of the class representing the node, and other irrelevant details. As a bonus, you now have a nice block of text you can dump into a log file, console message, or debugging window if the need arises.