> OO cowboys will want to have a whole polymorphic soup of collaborating objects. Say no to the OO-ist. When you nest and branch conditionals, all you need to do is read the code from top to bottom. Like a great novel, one simple linear prose of code. With the OO-overboard paradigm, it's like a terrible choose-your-own-adventure kid's book. You're constantly flipping between classes and juggling patterns and so many more complex concepts. Just if-things out and you'll be fine.<p>This is meant sarcastically, but I think this is actually one example of where a lot of backlash against OO comes from, especially in 2022.
> Be Defensive - They're out to Get Your Code! - Defensively assert about the state of parameters passed in methods, constructors, and mid-method. If someone can pass in a null, you've left your guard down. You see, there are testing freaks out there that like to instantiate your object, or call a method under test and pass in nulls! Be aggressive in preventing this: rule your code with an iron fist! (And remember: it's not paranoia if they really are out to get you.)<p>This seems nuts to me? Sacrificing guarantees for the sake of testability? I mean defensive coding is one of good tools to fail early and with meaningful error messages and confidence on state within your method.<p>Moreover there are tons of points just to enable mocks'n'stuff. Why not just go higher up the abstraction layer and test things functionally there? It's more useful IMHO than some state object that may never be true in real life.<p>It reads like chasing some code coverage metric with fake state. When in reality, you may still get nullreferenceexception because your assumption on false state was incorrect. Or some method call there may fail with unhanled error code or whatever. It doesn't take branches for fully covered code to fail.<p>IMHO unittests are useful, but in limited scope.
> Depend on Concrete Classes - Tie things down to concrete classes - avoid interfaces wherever possible.<p>Well, yes, if those classes are implementation details why would you want otherwise?<p>Just to write “unit” tests with mocks all over the place that later will be broken after the first refactor?
The number 1 way to write untestable code - is to build your entire project without writing tests at all. Adding tests after the fact will make you want to kill yourself, whereas when you write tests either before or at the time of writing you will generally take the path of least resistance for writing code that can pass those tests.
Interesting how OO-focused this is. I work at Google today, in relatively old C++ and JS (now converted to TS) codebases, and we have no particular emphasis on forcing every function to be a method of a class. I don't think this has especially harmed testability.
These seem like a great way to write simple, high-performance code that is easy to write (because you just think about your actual problem vs. what someone who wants to ignore the problem wants -- what arbitrary slice are they expecting your code to cover?) and easily tested with simple integration tests that use quality fixtures which exercise your code thoroughly.<p>Endless classes of bugs eliminated through these "anti-patterns".<p>Guess what: if unit tests pass in an OO system, it doesn't mean a fucking thing, especially dynamic languages where simply changing a name in a large codebase is a risky operation.<p>And fuck virtual methods.
I was a huge fan of this article and Misko's accompanying talks back in 2012 or so, but a lot of this hasn't aged that well. These ideas all assume that in all cases, the class should be the unit under test, and you can see how this plays out in Angular, where the command-line tool will generate stub unit tests for every new component you create. If you fill out those tests with the aim of maximizing code coverage, then what you'll find is that all of those tests will need to be rewritten at the first refactor. On a personal project, that might be an enjoyable way to spend a bit of time, getting to understand your project better, but on a team with actual deadlines, there's not going to be enough time to do that. Either you remove the tests entirely, or you put off the refactor indefinitely.<p>Class-based unit tests lock you into a specific implementation, providing a checksum on the current behavior, making it as hard as possible to fix an actual bug when it's discovered. Collaborators-included unit tests on the other hand, do not break unless your product is broken. It can be harder to chase code coverage when you're doing laparoscopic surgery on your code, but the end result is that every conditional has an actual use case behind it, and if the requirements change, you can see the name of the test with the bad requirement and delete it.<p>I still think that a lot of the patterns that make your code testable are still good though. It's not always clear at what level of abstraction you want to start mocking. Do you mock HTTP? Do you mock a martialed API? Do you mock a business logic abstraction that depends on that API? If you use DI for all of these layers, then you don't have to get it right the first time. You have options when you're writing your tests, and can use concrete or mock collaborators according to whatever your current need is.
How to write untestable code? Use any of Google's cloud libraries. (Well, not just Google's).<p>Good luck testing your cloud functions, dataflows, BigQuery integrations etc. etc. etc.
What's the least testable code you've had the pleasure of dealing with?<p>From my first commercial software job: a few thousand lines of complex C++ (mix of decoding the output of a mathematical model mixed with business rules) structured as a single "god class" with heaps of internal state and a dozen methods -- comically all of the methods would return void and took no arguments, and would call each other. It took days of careful refactoring and consultations with a colleague until it was possible to instantiate one of the things in a test harness and do nothing with it. In some sense this was dead simple code as there were no dependencies on external resources or concurrency.
> C++ specific: Use non-virtual methods<p>Are they suggesting that all/most of my methods should be virtual? That's just nuts. Next they will say that all of the members should be public, since it's easier to test that way.
This article actually has educational value other than laughs. Counter-examples are sometimes easier to remember than straight examples, especially if you're reading about pitfalls - your brain will match those patterns when it sees you're writing them. A text that explains what the wrong way looks like and mocks it, in my opinion, will be more effective than a text that explains the right way in great detail.
Hah! I actually took this sort of joke further than I probably should have a while ago with this <a href="https://boyter.org/posts/expert-excuses-for-not-writing-unit-tests/" rel="nofollow">https://boyter.org/posts/expert-excuses-for-not-writing-unit...</a><p>It also covers how to avoid writing them to begin with.
Following Pareto principles: 80% of tests should be convered with good static typed language and a clean, layered structure.<p>Only 20% left is to focus on testing business logic.
November 5, 2009 (29 comments) <a href="https://news.ycombinator.com/item?id=924859" rel="nofollow">https://news.ycombinator.com/item?id=924859</a><p>I thought I'd seen this here more recently, but I guess it was another similar article.
I disagree with<p>>Create Utility Classes and Functions/Methods<p>This doesn't make something untestable. Take for example a max function. A max function such as Math.max is a part of a utility class, but it's easy to test.
Defensive programming is important. It's better to get an assertion at the API level about a null than an error message that + cannot add 42 and null.