Disclaimer
I just want to take the time here to point out that your argument essentially boils down to the distinction between waterfall (design before implementation before testing) and agile (test-driven, quick and dirty, continual refactoring).
Neither of these is universally superior to the other. Each approach has its own drawbacks. This answer is not trying to tell you that you have to follow TDD principles, it's only trying to highlight the benefits of doing so, since your question seems to be rooted in not understanding the benefits of the TDD way of doing things.
It didn't say to never improve your code
Having an idea of writing a test & make it work quickly, does not look intuitive for a good software engineering approach.
You're likely not interpreting this the way it was intended.
"writing a test & make it work quickly and then never touch or improve it again" is a bad software engineering approach. But the resource you're quoting never said that you shouldn't refactor, it just said to keep it simple when you're still on the stage of getting it to work (i.e. getting a test to turn green).
Time wastage & how to avoid it
For a given requirement, a simple solution design that make us think about creating good abstractions and then Implement with testing looks very intuitive.
The intention is good but you're forgetting about the drawbacks. Taking a "design first, implement second, test third" approach is susceptible to two major issues:
- Overengineering (designs that are more complex than they needed to be)
- Wasted implementation effort (when the tests show you were doing something wrong)
The red/green approach inverts that order of operations, so that you can better avoid these weak spots.
- Write a test which compiles but fails.
- Implement a quick and dirty solution until the test succeeds.
- Improve upon that solution (refactoring) while making sure that the test doesn't start failing again.
This prevents the weaknesses mentioned before:
- You're less likely to overengineer as you have concrete evidence on exactly how complex your implementation is (rather than guessing what it will be), so you can better tailor your design to your implementation
- Since you can run your test every step of the way, any mistake you make is immediately noticed, and you don't find out about it much later in the process.
Direct feedback to your questions
Is TDD a brain wash?
No, but it does shake up the fundamental approach to software development. When you first encounter something that is radically different from the ground up, it's very hard to know whether this new thing is wrong or whether you just don't get it (yet).
Based on the prevalence of TDD in the field of software engineering, it's safe to say that it does work, so the logical consequence is that you're not quite understanding its principles (yet).
If you cling to the (non-TDD) fundamentals you know and love and don't want to reconsider them, then you're not in the right mindset to accept TDD as a different-but-equally-valid methodology.
Why would a good Software engineer, make the test work quickly, committing whatever sins necessary in process?
Because that software engineer minimizes time wastage while also not lowering code quality.
By keeping the implement/test feedback loop as short as possible, they maximize their efficiency at finding and dealing with any error they may have made as the error would have been made recently (as opposed to a long time ago, where your memory is fuzzier).
Why would the refactor stage be applied after completing a dumb green stage?
By implementing a first (temporary, working) solution, they more concretely define the domain for which they will then design an appropriate second (cleaner, more abstracted) solution), thus making sure they don't over-engineer things or regress constantly, both of which waste time on things that are not necessary or can be avoided.
Are those test cases(in Red stage) missing in non-TDD approach?
Red stage test cases are beneficial in two ways:
- They force you to define an interface without using your knowledge of the implementation details (since the implementation doesn't exist yet).
- You set up a testing suite that will then support your implementation and refactoring stages, which means you get more direct and accurate feedback about the consequences of any changes you make. Think of it like having a personal code reviewer who's constantly looking over your shoulder and immediately pointing out any mistakes you make, instead of telling you a week later. Sort of like what your IDE already does for you (the red squiggles), but using a bigger picture.
For a given requirement, a simple solution design that make us think about creating good abstractions and then Implement with testing looks very intuitive.
The design phase is comprised of two main considerations:
- Separating the interface from the implementation, by abstracting an interface that focuses on communicating with the outside world.
- Design patterns that streamline the internal implementation.
I want to draw your attention to the first bullet point I listed for the red stage test cases.
By forcing a developer to write tests based on an interface without an implementation, the developer is forced to design the interface in a pure manner (without knowing any implementation details, since that stage hasn't happened yet).
This already covers the first bullet point, separating the interface from the implementation. It's beneficial to do this before the implementation stage, as it means that you're not distracted by having implementation knowledge that you shouldn't be relying on when defining the interface.
The second bullet point, implementing design patterns to streamline the internal code, should only occur when there is internal code (i.e. after the implementation stage), since the choice of design pattern completely depends on the actual code being used. Here, you should rely on implementation knowledge, which is why the abstraction stage comes after the implementation stage.