7 ms·
How Does TDD Affect Design?
- arthurjj 12y agoAn issue I have with TDD is that the time spent supporting it is often better spent on thinking about the design
- Pacabel 12y agoIs that a problem with TDD, or is that just a problem with teams choosing to use bad tooling and processes? I've seen teams waste a lot of time trying to get various CI systems integrating with various TDD frameworks, generating lots of reports that nobody really ever looks at. Or they waste a lot of time writing elaborate frameworks that build upon some existing TDD framework. But all of that has basically nothing to do with TDD. It has nothing to do with the act of writing tests first, and developing the actual code based on the success or failure of those tests.
- crdoconnor 12y agoWriting tests takes time and is an investment and should be treated as such. Too many zealots try to pretend that it's a zero cost design habit.
- skj 12y agoI think that they would argue that it's a positive net benefit design habit.
- crdoconnor 12y agoThe OP mentions a number of antipatterns that only come about due to TDD. So no, not necessarily. Not unless it's done correctly and in the right circumstances.
- isuraed 12y agoUltimately, the only tests that matter are system tests (feature tests). These are the tests that ensure you're delivering the correct result to the customer. In practice I find unit level testing a pointless exercise.
- jxf 12y agoSystem-level tests are important, but they're not much help with refactoring code. For some applications, particularly in my domain of analytics, it's very important that we can do that effectively.
- crdoconnor 12y agoWhen you're refactoring code to reduce coupling, you absolutely HAVE to do it with system level tests. Doing it with unit tests simply means that you'll end up writing the test, refactoring and then completely REwriting the tests AGAIN to get them all to pass because you're changing the method contracts and the objects being mocked. Tests that fail every time you refactor are totally meaningless and a waste of time. They don't detect bugs. They just detect changed code.
- ollysb 12y ago>> When you're refactoring code to reduce coupling, you absolutely HAVE to do it with system level tests. I think this comes about because tests are written against every class in your system. I find unit tests are far more useful if you focus on testing abstractions rather than every single class e.g. you have a reporting abstraction in your code, instead of testing every class used within that abstraction you only test the public API that you want to expose. This allows you to do black box testing which is infinitely better when it comes to refactoring, you should be able to restructure the internals of that particular API without having to change your unit tests at all. My feeling is that a lot of the frustration with TDD at the moment is that people are writing tests for every public method in their system. If you focus more on the behaviour of your abstractions you gain a lot more freedom when refactoring and can greatly reduce the number of tests you write without reducing coverage.
- jacknews 12y ago"TDD doesn't create design. You do." Sorry, but this is akin the fallacious "guns don't kill people" argument. Well, of course not, but they certainly make it easier. So does TDD make good design easier, or harder? I'm afraid that, for me, the article didn't provide an answer, or even anything bu the most superficial insight into the issues. For example, coding is often about exploring a problem, and in these cases I think TDD is a hindrance. But whatever the overall argument, TDD certainly changes how things are designed, and what kinds of things are easy or difficult to achieve. Indeed, writing tests can sometimes be more challenging than the code itself. Now that would be something worth further discussion.
- crdoconnor 12y ago>So does TDD make good design easier, or harder? I'm afraid that, for me, the article didn't provide an answer, or even anything bu the most superficial insight into the issues. It gives a perfectly fine answer: design is as hard with TDD as without TDD. Doing TDD simply changes the type of design mistakes that you're likely to make. For what it's worth, he nailed the design antipatterns. I've come across every single one of the ones he mentioned.
- nawitus 12y agoA more interesting question is whether or not TDD leads to better design or not. Even if TDD doesn't change the difficult of good design, it can empirically affect the level of design. Here's a theoretical example of how that's possible: changing the design before tests are written and without using TDD requires less time than when using TDD. The reason (hypothetically) is simple: you need to rewrite tests more if you're using TDD. This might lead developers to redesign software slightly less on average if they're using TDD.
- benihana 12y ago>But whatever the overall argument, TDD certainly changes how things are designed I think you missed this point completely, and you made it hard to ever get it when you compared TDD to a highly contentious issue in guns - rather than this just being an issue of methodology in coding, it's now a political issue as well. Regardless, TDD doesn't change how things are designed. TDD is a methodology, a way of doing things. It's a tool - it can't change things without human input. TDD does not change design; it can't. It only amplifies a person's ability to do that by making poor abstractions and weak boundaries clear and that is the power of it as a methodology.
- deleted 12y ago[deleted]
- hibikir 12y agoThere's two parts to TDD: what it does for you when you are writing a new piece of code, or what it does when you come back to it. IMHO, the advantages when you are writing new code are vastly overstated. A former employer mandated TDD, and I did my best to follow their practices. It doesn't lead me to better modularization, more maintainable code, or really, any less bugs. However, going back to code that actually has a real test suite around it is invaluable. It's not really because the tests prevent you from breaking the code: More often than not, it's the test that needs fixing. The value comes from the tests behaving as living documentation. It lets me see why someone, at some point, wanted the code there. Then I can evaluate if the reasons are still valid, and wonder if the old requirements still make sense, or whether my updated requirements are going to be an issue, because someone forgot some byzantine case that the tests just reminded us of. Now, the question is whether we are better off getting here through TDD and a quest for full test coverage, or something higher level, like Specification by Example. But either way, if the domain is complex enough, either will cover a need that static, non-executable documentation handles way worse, because old school paper documentation goes stale very quickly. So how does TDD affect design? Mostly by making sure that you aren't writing pieces of code that are so complicated they are untestable, and frankly, you should probably be avoiding that in the first place.
- Retric 12y agoIMO what TDD does right is provide documentation that people are forced to maintain. It's not uncommon to see the same bug show up regularly because something that works is fragile. As long as coders don't do X everything works and fixing it would take weeks or months thus it's left lone. So, of course every new developer does X generally 2-3 times. And that's the secret TDD is almost useless for simple projects but grow some cruft and tests stop seeming so pointless. Which, IMO suggests you add tests mostly around that cruft.
- claudiusd 12y agoTDD doesn't lead to better or worse design, your testing strategy does. TDD changes your approach to testing, but if you choose a poor test strategy then TDD or not, you're going to have a bad time. I hate to say it, but this is yet another misleading article that is doing the programming public a disservice. Here's my testing strategy (YMMV): 1. Feature tests for the "happy path" - make sure the system requirements work as described from the user's perspective with full integration. I usually just write a couple. 2. Integration tests for high-level system components - these are more functional in nature but capture all boundary cases for the components that sit closest to the user. These are my saving grace when refactoring. 3. Unit tests for methods and other low-level components - these primarily serve my "integration tests" by factoring out the functionality of low-level components and allowing me to focus on their integration. Here I'll use mocking if it's convenient, and when refactoring these usually get re-written. Notice that this strategy says nothing about TDD, but I would argue that following the principles of TDD makes this strategy strictly work better.
- nawitus 12y ago>TDD doesn't lead to better or worse design, your testing strategy does. That's an empirical question. Do you know any studies that actually test this? It's certainly possible that TDD leads to worse designs or leads to better designs.
- morganherlocker 12y agoJust about everyone writes tests first. You have some sort of script that you are running against the real code you are working on to see if it is doing what it should be doing. The only real difference between true "TDD" and everyone else, is that the TDD folks save this script to be run later. I might be so far down the TDD trail that I have lost perspective, but do people really write huge swaths of code without writing scripts to run the code and see what it is doing? Those little scripts are tests, and with a tiny bit of formatting, they can be made to output something consistently useful like TAP[1], instead of inconsistent console logs. This whole debate seems like strawmen fighting strawmen. [1] http://testanything.org/ http://testanything.org/
- nawitus 12y agoI haven't heard about people writing some sort of scripts to run "against" the real code. Most people do manual integration testing by running the application, and doing some light debugging, and then when it starts to work, write a few tests (if any).
- taeric 12y agoImagine if they could codify their manual integration tests in such a way that they could be automated? Not necessarily a unit test, but still a test. Likely a valuable one.
- nawitus 12y agoYes, obviously we do that, but after the feature is working. It's not practical to write it before implementing it at least with the stack I'm working on currently. We use Selenium+Protractor for integration tests, and these tests are implementation specific.
- pessimizer 12y agoI do it, except they're not scripts, but really high level functions and objects/processes that create and delete themselves. And although I'm not explicitly TDD at all, those functions often end up becoming tests (or informing test writing) by production time. Applications I write are ultimately experiments in the interop of N different libraries that I haven't used in quite this combination before, so the first code I write is usually checking to see if things will work in combination in the way that I think they will. That involves writing code that has the stripped down functionality of the parts of the design that I thought could benefit by using those libraries. The last thing I finalize is the highest level logic, and by the time I write it, I know it's going to work.
- vjeux 12y agoJest[1], the JavaScript testing framework used at Facebook attempts to lessen two of the mentionned impacts of TDD > TDD works best with fast tests and rapid feedback. In search of speed, some people use mocks in a way that locks their production code in place. Ironically, this makes refactoring very difficult, which prevents designs from being improved. Jest automatically generates mocks for you. This reduces the cost invested in writing mocks and therefore lessen the burden of refactoring your code. But if you change the interface of your module, the generated mock will change and break all the tests that depends on it. Which is good! > Also in search of speed, some people make very elaborate dependency-injection tools and structures, as well as unnecessary interfaces or classes, just so they can mock out dependencies for testing. This leads to overly complex, hard to understand code. By mocking at the module system level, most of your code is able to be mocked without doing important refactoring. [1] http://facebook.github.io/jest/ http://facebook.github.io/jest/
- overgard 12y agoI'm generally for testing, but I have noticed a phenomenon that happens a lot: basically, the tests start influencing the actual design (in not a good way). To get the tests working, you end up having to write a lot more infrastructure and abstraction to support it. So you end up with 6 classes where one would do, because you end up needing an extra interface, and then impl, and then mocks, and dependency injection and on and on. Not to mention extra dev infrastructure like mock rest servers and so on. This seems more prevalent in languages where the type system is pretty primitive and concise code isn't as valued (ie: java). It seems like mostly a non-issue in dynamic languages like python and ruby (where honestly I think the tests are way more useful anyway) To me, the problem is that each line of code should always be treated as a cost. More code is more cost. And if you're writing 6 classes where one or two could do, you're creating a lot of extra code. And we'll just ignore the time spent writing the test and assume that it'll pay itself back in the future, I'm just talking about the extra maintenance costs of having 6 classes hanging around and the extra time it takes to run those tests every time you compile. I guess what it comes down to is not testing for dumb things. You should be writing tests against stuff you probably think could break. I'm not really big on the idea of 100% test coverage. I think once you're testing "most" of the code the cost/benefit of way more tests is mostly just in satisfying the OCD types.
- lectrick 12y ago> To get the tests working, you end up having to write a lot more infrastructure and abstraction to support it. So you end up with 6 classes where one would do, because you end up needing an extra interface, and then impl, and then mocks, and dependency injection and on and on. I have found this to be true only when having to work with code that was not written with TDD. For example, if you're writing a new Rack middleware, it will be EXTREMELY easy to TDD and unit test. The entire Rack design (while not devoid of criticism) is sort of ingeniously simple and was clearly written with TDD/unit testing in mind as a design priority. However, if you're interested in TDD'ing a Rails controller without loading your entire stack... Good luck with that. But Rails was NOT written with TDD, nor unit testing, in mind. It's evident right in the Rails lingo- a "unit test" in Rails is (was?) a test of the model code (and everything underneath). A "true" unit test doesn't test dependencies (so for example, an ACTUAL "unit test" of model code would abstract out the entire database and mock out all the CRUD operations). So I guess I could paraphrase what you're saying as, "If you have to TDD and, in the course of this particular development work, you have to work with code that was not written via TDD, you're going to have a bad time."
- tszming 12y agoThe last sentence of the above article is actually agreeing what dhh wanted to say :) >>So pay attention. Think about design. And if TDD is pushing you in a direction that makes your code worse, stop. Take a walk. Talk to a colleague. And look for a better way.
- jdlshore 12y agoAuthor here. I agree with DHH that people have created bad designs in the name of testability. I disagree with his conclusion that TDD should be thrown out, or that slow end-to-end tests are an adequate replacement. By "stop," I meant, "stop programming for a bit and think about a better way to design," not "stop using TDD." (I might agree that TDD is a bad choice for typical Rails apps, though. That's a pretty narrow case, and it says more about Rails than TDD.)
- SoftwareMaven 12y agoIt's funny that many people here seem to think TFA is a criticism of TDD, which is telling, IMO. It seems many people feel that TDD is always a net benefit to the software development process, regardless of how it is implemented. I saw the same thing with agile development and object oriented programming: just follow the buzz and software will be better. The author is not arguing against TDD. Instead, the authoR is simply stating it is possible to do it poorly. Everybody should be looking at every process they implement and deciding if it provides value and how it can be done better. Doing anything else is cargo cult development and doesn't lead to better software.