3 ms·
> I experienced this first hand when I first started learning to do TDD with unit tests. We had a badly designed system and we adopted a unit testing strategy t
by ryandv 3y ago
> I experienced this first hand when I first started learning to do TDD with unit tests. We had a badly designed system and we adopted a unit testing strategy that said every class needed to be tested independently from every other class. Testing this system was hard. It was a lot of effort with mocks and dependency wrangling to exercise our classes, and it produced brittle tests that never added any value in terms of the quality of the overall system.
> We were writing bad tests in a badly designed system and having a horrible time. The conclusion we came to was that this testing thing was for the birds. It was slowing us down and not really adding any benefit. Get rid of it.
If the tests are revealing that the system is "badly designed," the solution is not to throw away the tests in my view - it's to work on gradually refactoring the system to address the difficulties with testing it (of course subject to developer time/budget constraints...). The tests provide a gauge of the "internal quality" of the system (to take a concept introduced by the text "Growing Object-Oriented Software, Guided by Tests" [0]).
xUnit Patterns has published a list of "test smells" and possible defects they may indicate in the design of the system: http://xunitpatterns.com/Test%20Smells.html http://xunitpatterns.com/Test%20Smells.html
To be fair the author does call this point out in their conclusion:
> If testing your code is hard it means your code needs to be factored better. Stop what you’re doing and fix the design so that testing is easy again.
I would also add that adhering to the testing pyramid [1] principle is important, such that your system is not overly reliant on mocks (which can diverge from the actual "production" behaviour of the system) and/or brittle unit tests that break easily when changes to the system are introduced.
[0] https://www.google.ca/books/edition/Growing_Object_Oriented_Software_Guided/QJA3dM8Uix0C?hl=en&gbpv=1&dq=growing+object+oriented+software+guided+by+tests+%22internal+quality%22&pg=PT34&printsec=frontcover https://www.google.ca/books/edition/Growing_Object_Oriented_...
[1] https://martinfowler.com/articles/practical-test-pyramid.html https://martinfowler.com/articles/practical-test-pyramid.htm...
- drewcoo 3y agoHuzzah! I'll add that the meme of preferring integration tests to any other tests seems to stem from badly designed code. Looking at production code less closely disguises many sins.
- disgruntledphd2 3y agoTo be fair, while I agree with you, integration/e2e tests are much much easier to introduce into a legacy system, and it's really really easy to break things at the edges so they are definitely useful.
- MoreQARespect 3y ago>If the tests are revealing that the system is "badly designed," the solution is not to throw away the tests in my view - it's to work on gradually refactoring the system to address the difficulties with testing it I'm mystified that people keep suggesting this given the glaringly obvious chicken-egg problem inherent in doing it. If you think you can't safely refactor without reliable unit tests and you can't get reliable unit tests without refactoring then you are stuck. The ironic thing is that on the projects Ive dug out of a "not safe to refactor" quagmire (with high level integration tests), I always used to have an end goal in mind of refactoring to make the code more unit testable in the end. It felt like the "right" thing to do. That was just the industry dogma talking though. In practice by the time I reached the point where we could do that there was usually no point. Refactoring towards Unit testability had a low/negative ROI at that point, with code that was already safe to change.
- ryandv 3y ago> If you think you can't safely refactor without reliable unit tests and you can't get reliable unit tests without refactoring then you are stuck. Books have been written on exactly this problem; Michael Feathers' "Working Effectively with Legacy Code" comes to mind, where he explicitly names this problem as "The Legacy Code Dilemma:" > When we change code, we should have tests in place. To put tests in place, we often have to change code. It's not exactly an easy problem (hence the existence of the book), but there do exist techniques for getting around it - speaking very generally, finding "seams" (places where the behaviour of the system can be modified without changing the source code), breaking dependencies, and gradually getting smaller and smaller units of the system into test harnesses. Sometimes the code does need to change in order to enable testing: > Is it safe to do these refactorings without tests? It can be. [...] The trick is to do these initial refactorings very conservatively. Martin Fowler has catalogued [0] some of these "safe moves" or refactorings one can make; Feathers also recommends the use of tooling and automated refactoring support (provided one understands how safe those tools are, and what guarantees they offer) in order to make these initial refactorings to get the code under test. Whether or not this is actually worth the time invested, is another matter, and probably one far more complex. [0] https://refactoring.com/catalog/ https://refactoring.com/catalog/ [1] https://archive.org/details/working-effectively-with-legacy-code https://archive.org/details/working-effectively-with-legacy-...