perry_blueberry said:
That's also a benefit of them. I think a lot of clean coding practices are quite arbitrary and hard to remember. But what's certain to me is that when I start writing units for my functions I become more-or-less forced to write a clean API with clear separation, since testing it otherwise would be a PITA.
In practice I often find this to be a circular argument, in that dedicated fans of unit testing tend to favour testable APIs even if they make the code worse in some other ways. A common one is the debate over whether a private function should be testable. If you only test it via public functions, it's hard to guarantee good coverage. So people get tempted to expose it in some way, which now means that implementation details have leaked into the interface.
Encapsulation is one of the first victims of comprehensive testing. Often an object can't be tested adequately in isolation without being able to swap out its components. So you end up adding accessors or constructor arguments to inject replacement components, many of which never need replacing except for the case of unit testing. But again the interface has grown to accommodate this. The solution then is to outsource the now-complex construction of the object to something else, or even to use a whole dependency injection system to knit your objects together, and this isn't free. It adds complexity and overhead.
The question is - is it worth it? In a workplace of averagely-skilled engineers churning out long-lived business software, the answer is probably yes. The up-front cost is necessary to get a baseline of quality and the ongoing cost is matched by the ongoing need to remain reliable in the face of changes. But is that the case for game development? There's always a cost/benefit analysis to be made.
The easiest code to test is functional - you push an input into a function and you can verify the output.
The next easiest code to test is procedural transactions - you have a state, there's some input or an event, a procedure takes that input and mutates a small bit of the state in a predictable way, and you can verify that the state you expected to change did so - and hopefully, that nothing else did.
The hardest code to test is simulation-style code - you have a large state, time passes, and much - sometimes all - the state mutates based on a large set of rules, and on the rest of the state. It's not always clear what is expected to have changed, or to what values.
I've worked in ‘business’ software (web, telecoms, apps) and it's mostly the first two. And I've worked in games, and it's large amounts of the last one. And given that game code is much harder to test, those tests have to work doubly hard to prove their worth. You can (and should) be able to pick apart the smaller units of state change to understand them in isolation. But it's harder, and so often it makes more sense to just concentrate on writing quality code than on taking the extra time to test it, especially given the shorter shelf-life of most game code and the slightly higher level of competence (which is not universal, but on average).