This is my perception as a non-TDD person. Maybe someone can validate if this makes sense or not.
This is my perception as a non-TDD person. Maybe someone can validate if this makes sense or not.
Tests don’t help you design software, they help verify properties of the implementation. For non-trivial greenfield systems, the full set of properties (which is a very high dimensionality tradeoff space) and their expression in the implementation isn’t really knowable ahead of time.
Also, this is not specific to software. Defining how you will validate a system, before you go build the system, is simply good engineering.
Hardcoding specific implementation detail assumptions into the design of complex non-trivial systems so that you can write a test is a brittle way to go about writing software. There is a reason that waterfall-style never seems to produce good systems in a reasonable amount of time or money.
I write non-trivial greenfield systems and put inordinate amounts of design work into them (literally years in some cases). I have never found a case where that allowed me to write useful tests prior to implementation. At a minimum you need a complete architectural skeleton that runs and has had its non-functional properties verified analytically before you can write tests that you’ll likely use.
I’m not against writing tests. I invest far more in testing than most devs due to atypically high reliability and robustness requirements. I’m against the complete waste of time that would be writing tests before there is sufficient implementation to design maximally efficient and appropriate tests. Testing of some software is extremely expensive in both time and money, so optimizing test effectiveness and efficiency becomes valuable.
Starting with TDD is like being a general whose recommendation to every international politics issue is to commence training for a combined arms assault on the capital of the country's historical enemy, because that's something that can be iterated to provide good estimates and error-free execution. Except, that general misses other, possibly much more effective approaches, such as airstrikes, assassinations, supplying domestic terrorists in enemy territory, or... not shooting anything at all because we're not at war and the problem can be trivially solved by respective VPs over some drinks. Or the problem isn't even a problem.
Keep the TDD General around for when you're actually fighting a land war - but for the love of $deity, don't let him drive international policy in peace time.
Or, it should be anyway. People talk about how it "drives" design but I dont think it should. I think tests should be as design-agnostic as possible, providing freedom to redesign.
I always start with an end to end test. These dont force you to commit to anything.
I don't agree. TDD is only relevant regarding the interfaces used by units of behavior. Software architecture isn't something that you design or verify or even assess with unit tests. Software architecture is purely a design task, and redoing software architecture is a kin to rewriting the whole project.
In performance-engineered systems, a component’s behavior in isolation is not its behavior in the system, and that behavior determines correctness. Basically the same fallacy as most micro-benchmarking. Resource availability and locality vary dynamically, and the software design has to account for this, which is extremely non-trivial to do without code you can measure in the system context.
And yes, modifying software architectures to address design issues that show up in implementation details or to slice some non-obvious tradeoffs differently is expensive, but is almost always required to some extent. That’s a cost of business if you are doing non-trivial greenfield software, it comes with the territory.
If anything it helps.
For example, systems software is mostly about testing system behavior under various loads and resource constraints. The ability to even measure that reliably without the measurement interfering with the software operation is dependent on a lot of implementation detail.
Call it what you like. Some people would be insistent that this is TDD. Some would say it isnt because it's not test driven design. Some say it is TFD. I dont really care what it's called, for me it doesnt make sense to develop serious non-spike/non-POC code any other way.
It's one of the few things in tech where my opinion has hardened and not softened over the years.
"Unit tests" on the other hand, blech, sometimes useful, very overused.
TDD stands for "Test-Driven Development" not Design. Your design could change radically after creating the initial tests, they should not be used as a way to freeze your system unless there really is a true finished and complete state for it. (This is true for any test suite, not just those developed doing TDD.)
Which implies a cost of at least 2x in dev hours. One should always evaluate if it is worth double or triple LoCs to maintain.
If you don't bother with tests, discussing TDD is like famine victims discussing nutritional levels reported in food labels.
> My tests are usually bigger than my implementation.
That's perfectly fine if you feel your tests are important. Getting back to the topic, TDD does not enforce coverage targets or even minimum coverage bars.
No, not really. I have no idea what led you to believe that.
Think about this for a moment: does TDD force you to comply with a target coverage metric? Is the TDD police going to knock on your door if you leave out a usecases? What about a whole component?
How many seconds does it cost you to add a test checking for the happy path?
Of course it does. You only write production code to make tests pass. Hence 100% coverage.
When would any not-covered code be written in TDD?
No, not really.
TDD just requires red-green tests when adding features. You can add a test that only checks for the happy path.
More to the point, nothing forces TDD practitioners to succumb to the hypothetical slippery slope fallacy of having to follow a fundamentalist path 100% of the time. Why do you believe that nonsense if even unit test best practices stay clear of 100% test coverages?
Yes, really.
If you know what is TDD then you wouldn't make mistakes such as believing each and every single unit of code must have a test. That's specious reasoning and slippery slope sloppy thinking.
> But then any discussions about the properties of TDD are pointless.
Not really. You're just succumbing to slippery slope sloppy thinking. Adding a red test and working your way up to a green test is self explanatory. Being contrarian just wastes everyone's time.
TDD is test driven development but it should not freeze your system. If you make system testable it is the opposite of frozen. So if you design system to be testable it already should be more flexible.
The design here is one notch of abstraction above.
Also tests should not freeze anything as they should be removed if not needed. If you write 20 tests during development it doesn’t mean those 20 tests are needed for future maintenance.
But I have colleagues (working in other projects) who told me how they are succesfully applying TDD to refactor an old codebase. They start with big, not-so-"Unit" tests to make sure that they don't break the thing at any step, and then they move to more specialized tests when tackling specific parts of the code.
Nothing in your example prohibits or even hinders TDD. The only thing required by TDD is that you specify an interface and add tests to specify what behavior you expect from them. The moment you have something you can compile and run, your TDD path is completely open.
Also, I'm noting the hints of the same specious reasoning that plagues discussions on microservices, mainly the "it's-all-or-nothing" fallacy. You can benefit from TDD if you, say, are writing a RESTful interface and start off by only adding unit tests checking for controller behavior. If your project can live with 0% line coverage, it can certainly live with TDD buying you 20% line coverage.
The codebase is mature and it's architecture is likely well established. It's easier to write tests for small changes in a very established system than it is to write tests for a system that's still in flux.
In most cases that I've seen, this isn't done because there's no financial upside to doing so. When your bonus is tied to velocity, not quality, any effort on testing is too much effort.
The main idea of writing a test which fails and then fixing it with the code change applies almost anywhere.
E.g. when fixing a bug, write a test case that fails because of the bug, and passes due to the bugfix.
You don't necessarily need the refactor step, though. The continuous refactoring is the greenfield aspect, perhaps.
Refactoring can leave a trail of TDD tests that used to be independent, but are now tied together and redundant.
Yes, my thoughts exactly. At its core, TDD forces a workflow where the interface is the first thing laid out, and the expected behavior follows. Therefore when designing new components, it becomes readily apparent when the interface doesn't work, even before anyone invests any time adding the missing behavior.