I advocate the test-first approach.
Write the test, then write the code that satisfies the test. This forces you to think about how the code is used, how it's tested, exactly what it's effects and responsibilities are and whether or not it has too many of them. Ugly tests indicate the code under test is ugly. The solution is to clean the code, and make the tests just as beautiful. If you can't think of a test, then you don't adequately know what the thing your trying to write is supposed to be doing, what it is responsible for.
You may not be able to test certain aspects of the lowest levels of your code automatically (I disagree with this notion, but accept it may be more effort than it's worth in many situations), but anything using those levels can be tested by mocking those lowest levels. This keeps responsibilities clean and this forces you to keep the low level at the low level, and separate your game logic from your inputs and outputs. And that's the main advantage of writing tests. If you do it well, commit to it and importantly if you accept the benefits given to your code by taking the time to refactor, the code practically keeps itself clean. The time invested in tests and refactoring is regained in the reduced number of bugs, the less time spent in the debugger and the ease of making changes to clean code.
And if you need to change legacy code that lacks tests, wrapping it in tests before making the changes also accomplishes this by highlighting the issues and providing a safety net as you refactor.
Things that violate the single responsibility principle and inversion of control become so cumbersome to test that you need to clean them up to maintain productivity. Which is an important thing to remember: When the tests become ugly, it's not the fault of testing, but the code under test.