I especially dont see what is gained by writing the test after.
I especially dont see what is gained by writing the test after.
A small community of programmers, with a disproportionately large audience, foretold that practicing test-driven development would produce great benefits; over twenty five years the audience has found that not to be the case.
Compare with "continuous integration" - here, the immediate returns of trying the proposed discipline were so good that pretty much everybody who tried the experiment got positive returns, and leaned into it, and now CI (and later CD) are _everywhere_.
As for what is gained, try this spelling: test driven development adds load to your interfaces at a time when you know the least about the problem you are trying to solve, which is to say the period where having your interfaces be flexible is valuable.
And thus, the technique gets criticism from both ends -- that design work that should have been done up front is deferred (making the design more difficult to change, therefore introducing costs/delays), and that the investment is being made in testing before you have a clear understanding for which tests are going to be sensitive to the actual errors that you introduce creating the code (thereby both increasing the amount of "waste" in the test suite, in addition to increasing the risk of needing test rewrites).
The situation is further not improved by (a) the fact that most TDD demonstrations are problems that are small, stable problems that you can solve in about an hour with any technique at all and (b) the designs produced in support of the TDD practice aren't clearly an improvement on "just doing it", and in some notable cases have been much much worse.
So if it is working for you: GREAT, keep it up; no reason for you not to reap the benefits if your local conditions are such that TDD gives you the best positive return on your investment.
If Im writing a single line of production code I should know as much as possible what requirements problem Im actually trying to solve with it first, no?
This is actually dovetails into a benefit to writing the test first. If you flesh out a user story scenario in the form of an executable test it can provoke new questions ("hm, actually I'd need the user ID on this new endpoint to satisfy this requirement...") and you can more quickly return to stakeholders ("can you send me a user ID in this API call?") and "fix" your "requirements bugs" before making more expensive lower level changes to the code.
This outside-in "flipping between one layer and the layer directly beneath it" is very effective at properly refining requirements, tests and architecture.
>And thus, the technique gets criticism from both ends -- that design work that should have been done up front is deferred
I dont think "design work" should be done up front if you can help it. I've always felt that the very best architecture emerges as a result of aggressive refactoring done within the confines of a complete set of tests that made as few architectural assumptions as possible. Why? Coz we're all bad at predicting the future and it's better if we dont try.
This is a mostly separate issue from TDD though.
I will write as many tests as I need to feel confident, which depends on context.
And integration tests give me a lot more confidence than mocked unit tests.
The cost/benefit of writing a test before just consistently exceeded doing it after for me.
Same for integration, e2e or unit tests (there's never been a rule that says you can only TDD with a unit test).
The cost/benefit trade off for tests with mocks vs. database is a different topic - orthogonal to the practise of red/green/refactor, and one where IMO the trade offs are much less obvious.
It sometimes makes sense for unit tests; I'll occasionally do that when I'm unsure about the API of the code I'm writing since it allows me to spend some time in the user's shoes.
But like I said, I don't do fundamentalism.
I assume you mean versus writing it first, rather than versus not writing it at all.
I've found that TDD works well for bottom-up coding, but not so well for top-down.
With bottom up, I can write the test for a piece at the bottom, write the code to pass the test, and move on. With top-down, if I write the test first, it might be a long while before I have that top-level working, because the bottom bits don't exist yet.
When I feel it's better to write things top-down, I'll often use TDD for the bottom bits I need to write, but for the bits above that, I'll write the tests "on my way back up".
The greatest value in tests is that they help prevent future changes from breaking existing functionality. Writing the test after you write the implementation is equally useful for achieving that as writing the test before you write the implementation.
Requiring the test before writing the code also ensures you dont forget to write a test to match the scenario.
So what is gained by test after... is that it is almost as good?
I still dont get it.
This order also helps verify I didn't typo something in the test itself and end up TDD-ing myself into broken code.
You don't need something to work with to write it. You can, by definition, write an e2e test against an app that doesnt exist.
This test isnt special as far as TDD is concerned - red-green-refactor works the same way.
Im sensing a pattern in the answers to my question though. I keep getting "well, if you assume TDD is only done with low level unit tests..."
Completely wrong.
Even with your example, there's an initial exploratory stage where you're still figuring out the interface that the tests would use. I, personally, am not capable of using something that doesn't exist. I have to make that initial version first before I can use it in a test.
Quick edit aside: This is also why I rarely work top-down or bottom-up, I work mostly throughline - following the data flow and jumping up and down the abstraction stack as needed.
What happens when you then show it to stakeholders (e.g. other teams consuming your API, customers or UX people) or and they tell you to change it again?
Rewrite everything again?
Thats gonna be reaaaaaaaalllly labor intensive and could damage your code base too.
Im equally perplexed about why people dont try to build top down. It's one of those few things in programming that always makes sense regardless of circumstance.
> Rewrite everything again?
> Thats gonna be reaaaaaaaalllly labor intensive and could damage your code base too.
Why would I do that? Only the thing they have issue with would need to be changed, it wouldn't take any longer than another way of doing it.
You seem to have forgotten what I said, something needs to exist for me to work with. Well, in this "stakeholders want something changed", something exists. It's not a rewrite from scratch.
If you change the spec (e.g. changing the contract on a REST API), you will probably need to consult to make sure it aligns with everybody's expectations. Does the team calling it even have the customer ID you've just decided to require on, say, this new endpoint?
>You seem to have forgotten what I said, something needs to exist for me to work with.
No. I'm assuming here that a code base exists and that you are mostly (if not 100%) familiar with it.
So you end up writing the unit's boilerplate, that doesn't yet do anything but needs to compile/run for a suite to test it throwing a adhoc error of the notImplemented sort or whatever, then break flow to set the test suite and check the reds (that aren't telling you anything useful because of course it's red), then actually write the code.
I find it flow-breaking and cumbersome for little to no gain.
For additions to existing code I find TDD more useful, but even then it's not unusual to decide to move logic around until deciding a final structure, so the units you end up with might be solidified later in time. Writing tests for scraped units is a waste of time then.
Right. In those situations I TDD with an e2e or integration test.
I dont get why youd restrict yourself to doing TDD with just with low level unit tests.
The type of test is, in my mind, a completely different topic to red-green-refactor and for the decision about which one to write I follow a set of rules which is also unconnected.
TDD is just red-green-refactor. It works with any test.
I only use that technique for pieces of code that really fit that well - usually functions that have a very strong relationship between their input and output - so I'll write tests first for those, but not for most of my other stuff.
Almost every user story I follow in production code follows the form of given/when/then scenario which can always be transformed into a test of some kind (e2e, integration, sometimes even unit).
Where it's something like "do x, y and z and then a graph appears" I find TDD with a snapshot test with, say, playwright works best.
If you're using snapshot tests (a technique I really like) surely you can't write the tests before the implementation, because you need the implementation in order to generate the snapshot?
(This is what I hate about the term TDD: sometimes it means test-first, sometimes it doesn't - which leads to frustrating conversations where people are talking past each other.)
I agree that "unit test"/"integration test" as a definition sucks horribly and leads to people talking past each other, but I think with TDD the main issue is that lots of people have developed a fixed and narrow idea of the kind of test you are "supposed" to write with it which makes the process miserable if the type of code doesnt fit that type of test.
The whole idea of a unit test being "the" kind of "default" test and being "tests a class/method as a unit" definitely needs to die.
No, it provides live feedback about whether your code is passing your tests
If you have written your tests poorly then set out to make the tests pass, then your tests become the target rather than the correct behavior
If you are continuously updating your tests while your code evolves because you missed test cases or your understanding of the behavior has improved, then writing the tests first didn't actually give you any value. In fact it just wasted a lot of your time
Write the code Manually test to verify correctness and to identify the test cases you have to write THEN write tests to protect against regressions