27 ms·
Test-induced design damage
- slavoingilizov 13y agoRails, rails. rails. Everything is about Rails. David is a good guy, but I'm starting to wonder if he's ever built a moderately complex system, involving integrations, message queues, several data stores, a handful of third-party libraries and APIs and deployed on more than 3 machines. TDD is a tool to manage complexity. It's an advice, not a recipe. Like any technology - it isn't a substitute for thinking.
- sjtgraham 13y ago> I'm starting to wonder if he's ever built a moderately complex system, involving integrations, message queues, several data stores, a handful of third-party libraries and APIs and deployed on more than 3 machines. Erm… Have you heard of 37Signals? > TDD is a tool to manage complexity. It's an advice, not a recipe. Like any technology - it isn't a substitute for thinking. I don't think you disagree with DHH here. The key point is TDD cargo-culting has encouraged codebases to become deformed beyond recognition in pursuit of unit isolation, which btw provides no guarantee that a system even works end to end.
- slavoingilizov 13y ago> which btw provides no guarantee that a system even works end to end. Nor shoud it. Some things you test in isolation, others you test together. I am not making fun of dhh or Basecamp (37signals is the company, not a system). I even read most of his stuff and admire him. All I'm saying is that there are many systems far more complex than Basecamp and when you cannot fit the whole thing into your head, TDD helps to divide and conquer. I am against blindly following TDD, but I am also against dismissing it because it gets in the way when building a Rails app.
- zsombor 13y agoI am quite sure that the Basecamp cluster contains tens of machines. It also features a rather clever caching design. Please don't try to justify a push towards simplicity as lack of ability to handle complexity.
- kasey_junk 13y agoA) I don't subscribe to the idea that building guis on data stores is in any way demeaning and/or easier than other kinds of development work. B) I do think that if that is the main kind of systems you write (and I could be mistaken but all of 37signals products look like that to me) it will impact on your design philosophies and your thinking about quality assurance in ways different than other sorts of systems. The requirements are completely different for a system that is taking info from a user, persisting, and giving it back to them later, than for a system that is processing information and acting on it. Those different requirements will drive different architectures and different QA processes. None of that means you can't document those requirements as automated tests at the beginning of your coding cycle.
- praptak 13y agoSame impression here, dated Jan 2013 after his diss on dependency injection, based on a single method toy example: http://david.heinemeierhansson.com/2012/dependency-injection-is-not-a-virtue.html http://david.heinemeierhansson.com/2012/dependency-injection...
- erichmond 13y agoThat's precisely the problem. Many in the rails community do use it as a substitute for thinking. In general, I'm not the biggest DHH supporter (although, he's made me a ton of money, indirectly, via rails), but I do like that he's stirring the pot here. Back in the 90s and early 2000s, I wrote tests, when needed. Sometimes before application code, sometimes after, it was a discretionary tool that I had in my arsenal that helped me both solve problems and feel confident that "my code won't break". At some point in time the majority of the rails community decided that if you don't test, you're a terrible programmer. Full Stop. The problem with this was that testing tools were terrible at the time, RSpec, before it's API solidified was breaking every other release, things like capybara and selenium and watir, always kinda worked, but not really, and you'd often spend 10 minutes writing the business logic, and then 40 minutes writing tests, getting them to pass, wrestling with external dependencies, etc. Furthermore, because people were practicing test-based application design, you were constantly re-writing your codebase and your test suite, because if you're designing for tests, you're not designing for the domain, and as domain requirements changed, and broke your testing model, you basically had to fix everything, and you couldn't get a handle on the domain because you had to be ruled by tests. All that said, I do think tests are useful. TDD isn't as useful for me. My thoughts are in line with Rich Hickey who said: "Life is short and there are only a finite number of hours in a day. So, we have to make choices about how we spend our time. If we spend it writing tests, that is time we are not spending doing something else. Each of us needs to assess how best to spend our time in order to maximize our results, both in quantity and quality. If people think that spending fifty percent of their time writing tests maximizes their results—okay for them. I’m sure that’s not true for me—I’d rather spend that time thinking about my problem. I’m certain that, for me, this produces better solutions, with fewer defects, than any other use of my time. A bad design with a complete test suite is still a bad design." From http://www.codequarterly.com/2011/rich-hickey/ http://www.codequarterly.com/2011/rich-hickey/
- DrJokepu 13y agoIn my experience as a system gets more and more complicated it is typically the interfaces between various things (e.g. integration) that become problematic. While the number of logic bugs increase linearly with the size of the system, interface / integration bugs increase exponentially. It is the integration tests that really keep madness at bay. TDD and unit tests can be useful, especially if you're working with a dynamic language, but nearly not as useful as integration tests and won't really help you with complexity.
- kasey_junk 13y agoIt's this assumption that TDD is all about unit tests that I find interesting. I drive my code with integration tests I write ahead of time as well. Maybe it is just the crowds I run in, but I've never encountered this dogmatic assumption that unit tests are the only tests you should have.
- MartinCron 13y agoI have seen that dogmatic assumption, but I feel that it has fallen out of fashion at least a little bit. Nobody is telling me that I am doing it wrong for insisting on tests at multiple layers of the pyramid.
- djur 13y agoThe TDD book I'm most familiar with, GOOS [1], advocates starting with integration tests and then using unit tests during the process of making the integration test work. This approach has always helped me stay focused on the real-world requirements of the feature while also having fast tests to rely on during development. [1]: http://www.growing-object-oriented-software.com/code.html http://www.growing-object-oriented-software.com/code.html
- platz 13y agoI consider GOOS the most credible authority on how to build a project with TDD methods. It even uses a staticly-typed language, which is interesting because the most vocal adherants of TDD tend to be using dynamically-typed languages. I've read the book, but I'm not such an avid TDD'er myself, mostly because I'd probably be doing it wrong for quite a while before getting it right. It'd be great if other people who've read the book had any insights here on how it related to DHH's opinions.
- dhh 13y agoEveryone has their own definition of complexity. I make no mince about a decade worth of developing Basecamp is where I draw my primary experience from. That system is small by web scale standards -- only 70 million requests/day, 1.5 terabyte of DB data, half a petabyte of file storage, two data centers, and about 100 physical machines -- but probably still larger than 97% of all Rails apps. Also, plenty of data stores (memcached, redis, multiple MySQLs, solr), many 3rd party libs, job servers, integrations, and more. So no, it's no Facebook or Yahoo or Google. But it also isn't a toy system, except in the sense that we're still having so much fun playing with it.
- slavoingilizov 13y agoI am not dismissing Basecamp. I am just saying that in a large portion of the software world, applications are waaaay more complex than normal rails apps. And in that context, TDD makes sense if only to manage complexity. Even if you are not Facebook, but say Airbnb. If their tests are not fast enough and they cannot trust them to make decisions, they wouldn't be able to deploy in a reasonable time. And when slow tests lead to infrequent deployments, that's when the real problems begin. (Airbnb is an arbitrary choice which came from the top of my mind, not anything specific) My gut feeling is that >50% of software development happens in those complex apps and not rails apps. So dismissing TDD is just yet another extreme viewpoint, which many people will unfortunately take for granted.
- edu 13y agoAFAIK Airbnb uses Ruby and Rails to some extent. An actual job offer lists it as a requisite: https://www.airbnb.com/jobs/departments/position/2192 https://www.airbnb.com/jobs/departments/position/2192
- mempko 13y agoI think the counter argument may be that TDD actually adds complexity to a system by destroying the architecture. So I am interested in what particular arguments you have in how TDD manages complexity instead of increasing it.
- pistle 13y ago
- enoptix 13y agoI echo his sentiment. Integration testing, especially when you have a JS frontend, makes much more sense. I never saw the point of controller tests and making sure a controller assigns variable @widgets with [widget] and all that nonsense. An integration test will identify all those problems and then some.
- sjtgraham 13y agoI agree. Instance variable assignment is an implementation detail, IMO it's more useful to describe their behaviour with a request spec for example.
- enoptix 13y agoYes, exactly. We use request specs to test regular HTTP requests to regular pages or our API. And then we use feature tests with Capybara and PhantomJS to load the app, click through it, and make it loads as it would in a customer's browser. This covers most of our application. And then we use regular unit tests for anything not customer facing, such as background jobs. But these unit tests make up only a small fraction of our test suite.
- lmm 13y agoThe point is to be able to unit-test business logic in the controller. If your app is just dumb crud end-to-end then sure, there's no point in anything but integration tests (and maybe not even those), but if you have any interesting logic then you can get much better coverage more efficiently in a unit test than having to run through the whole stack every time.
- jon-wood 13y agoFor my use case (building a reasonably complicated e-commerce platform) I find that the sweet spot tends to be integration testing to make sure the frontend is spitting out the right thing when going through the whole stack, and unit testing to make sure the correct logic is being applied when performing particular operations. That combination allows reasonably fast unit testing, because database interaction is stubbed out for that level, which gives a decent level of confidence that nothing major has been broken within a few seconds, and then a longer 10-15 minute integration test suite which checks the stack works as a whole.
- kasey_junk 13y agoI suspect my disagreements with DHHs last couple of blog posts have more to do with what each of us has seen in the wild than with any actual disagreement in principle. For instance, in my experience I more frequently encounter places that have way fewer tests than necessary and there is no consideration about how to verify requirements at all. Further in my experience, the GUI and database layers are the least interesting parts of the systems I work with. They truly are parts that can and do get swapped out with some regularity. I suppose if I worked mostly on systems where they were in essence gui's on top of databases with little logic in the middle, I wouldn't want to isolate those concerns either. It would be more trouble than it is worth. For instance, when I write small unixy command line utilities I very rarely test anything in unit tests. What would be the point? I can easily define the entirety of the specification in example tests that utilize the utility as a black box. I still do it first though...
- enoptix 13y agoIs swapping out the database layer so common you really need a complicated abstraction like the Repository model he mentioned? We've been running an app for 3 years and have never dropped ActiveRecord or Postgres.
- kasey_junk 13y agoI tend to not work on applications that work with a single data store. Some data will be stored on the file system, some in a traditional RDMS and some in a NoSql implementation. What data goes where, frequently changes and isn't really a major concern of the system, at least not the parts that need specification verification.
- enoptix 13y agoSo you have data that one day might go into an RDMS but the next day might go into file store? Do you constantly migrate old data between the two stores? What is the use case for this? Not being snarky, I'm genuinely curious. We use more than one datastore but that data usually stays put once its committed to one format. RDMS for most of the app, Redis for quick lists and cacheing. Flat files when necessary. But those models don't change their store ever unless its a major overhaul.
- ch4s3 13y ago" but does so by harming the clarity of the code through — usually through needless indirection and conceptual overhead" This argument feels a bit thin and unsubstantiated for the general case. I can see his criticism of hexagonal design applied to Rails, but he's using that as a straw man to attack TDD. I think he could better criticise the limitations of TTD by directly examining applications of RGR and other TTD pronciples.
- sjtgraham 13y agoIt's not really a straw man. Driving your application with tests at the unit level doesn't make a lot of sense, in the case of a web app at least. The BDD approach makes more sense to me. It's how I work and in my experience tends to inform design a lot better.
- ch4s3 13y agoI think saying hexagonal design is bad therefore TDD is bad qualifies as a straw man. Person 1 has position X. Person 2 disregards certain key points of X and instead presents the superficially similar position Y. The position Y is a distorted version of X. Here X = TDD is good. Y = hexagonal design could be good for rails Y =/= X I'm no TDD zealot, I just think DHH's argument here is weak.
- MartinCron 13y agoKind of off-topic, but it is nice to read a thoughtful post that isn't over-the-top flame inducing. Less emotional rhetoric than some recent TDD discussions.
- lmm 13y agoSeriously? It's full of snarky swipes at "true believers". I don't think DHH has made a sincere attempt to engage with the other side; this is knocking down a strawman again.
- adamors 13y ago> the simple controller is forbidden from talking directly to Active Record [..] This is not better. It is. The controller layer should be as dumb as possible, it shouldn't contain your (entire) application logic. It's a matter of single responsibility if anything. Also, I find it very sad that we're still discussing the usefulness of the active record pattern. Other than convenience, it has none. It's a pain to maintain an application that uses it once it reaches a certain level of complexity. And not just because of testability, it's a pain in the ass to replace/fine tune certain queries if you're calling active record methods in your controller.
- sanderjd 13y agoHonest question: what pattern for database access is better, and what are the best tools for said pattern? I don't necessarily mean just in Rails, but everywhere. ActiveRecord-the-ruby-library is incredibly mature and convenient to use, and for better or worse, encourages active-record-the-pattern. The repository pattern seems nicer in theory to me, but in practice, the best tool (in ruby) to implement it with still seems to be ActiveRecord, and then I find that I'm mostly delegating to the underlying AR object (because it already does everything!) and wondering what I've really gained. I was hoping DataMapper2/ROM[0] may have been a more straightforward but high-functioning replacement for AR, but it seems there has been no progress on it for quite some time. tldr; I'm wondering how you actually do this. Firstly, in Rails, but other acceptable answers are "other technologies do it in this other way, which is better than how Rails does it for these reasons". [0]: http://rom-rb.org/ http://rom-rb.org/
- adamors 13y agoI'm not using Rails (nor Ruby for that matter) so I can't comment on that part, but I found the repository pattern to be really useful. Using it with an active record is something I've seen other people do, and at least it gets the active record calls out of the controllers. It's not an optimal solution of course, but IMO it still beats plain active record.
- sanderjd 13y ago
- badman_ting 13y agoI really identify with some of the points he's making, they're observations I've made myself so it's nice to see someone with his clout bringing them up. I wonder about the design thing though - our code is in some ways a document of the circumstances surrounding it. Does it make sense to have it conform to some Platonic ideal, which we corrupt when we alter it to make it more testable? I'm really not sure about this, but I doubt it. Code ultimately needs to work in a given set of ways and that's our primary concern with it. Making the code "pure" (or just "easy to read" if you like) is a service to other developers who come along later. So, the tradeoff is testability for intelligibility. I can imagine a lot of scenarios where that tradeoff is a rational one.
- collyw 13y agoWe were having discussion about a collaborating groups architecture. The guy said something couldn't be done, because it would involve coupling two components. Again that is a nice ideal to aim towards, but surely functionality comes first.
- mwcampbell 13y agoI don't see why TDD proponents make a big deal about not touching the database. It's as if they haven't heard of SQLite's in-memory option, in which the database is just another data structure in RAM, which is all that their extra layers of objects are. True, with that setup, you're using SQLite for tests and, say, PostgreSQL in production. But is that any worse than using your own mock objects in tests? What am I missing?
- mickeyp 13y agoSQLite is not <your actual rdbms>... it's SQLite. A database that does not enforce column types and lacks most of the advanced features of a real rdbms. Using SQLite will work great right up until the point where it won't and you get your fingers burnt. If you separate your concerns properly you won't need to mock the database layer either. Mocking is just one part of the trifecta of good testing, along with Stubbing and Faking. For most things it would make more sense to fake the database layer or stub the database layer in your "logic" layer. However, if your application makes heavy use of the rdbms then you should test that layer too: In your integration tests and not your unit tests. Most places that interact with an RDBMS treat them like black boxes and not like a business layer of its own. You really need integration tests to ensure things like constraints and your business rules are captured properly... most people never bother. The real problem with TDD and its methodologies isn't TDD itself; it's people shoehorning about 10% of what proper testing should be into two narrow groups: stuff that you can do with "unit tests" and "things we can mock." There's a lot more to it than just those two things.
- sanderjd 13y agoUsing SQLite's in-memory option is basically just a convenient way to get database mock objects. I don't personally think it's any worse (or better) than using mock objects. However, I think a reasonable argument for why they might be worse is that you've introduced a lot of fake code (as in, not used in the real application) that can cause false-negatives or false-positives in your tests. That's true of libraries for mocks too, but they introduce way less code.
- lmm 13y agoSQLite probably qualifies as a mock in the sense DHH is talking about - as another reply said, there's no clear sharp line between "unit" and "integration". But to answer your question, what you may be missing is performance. I don't object to tests that use the database on ideological grounds, I object because they're slow. In-memory databases are faster, but still much slower than a true unit test.
- gregwebs 13y agoMaintaining giant test suites and trying to keep them running fast is why I am so glad to not be using Rails anymore. Dynamic languages don't scale well for me because the testing is difficult to scale. With Yesod (a Haskell web framework I help maintain) I have a fraction of the need for unit tests. The compiler already gives me the equivalent of 100% code coverage for catching basic errors. I can focus efforts on testing application logic and integration testing.
- sanderjd 13y agoAre you able to find consistent work building web applications with Haskell? It seems that many organizations are reticent to build on those sorts of technologies (not quite sure what else I'm including - maybe OCaml?) for very rational reasons - it is hard and expensive to find employees who are capable of being productive in them.
- nbouscal 13y agoThere seems to be a very common perception that it is hard and expensive to find employees who can be productive in less mainstream languages, but I rarely see evidence to back that up. I can't speak for Greg, but in my experience, hiring is hard in every language, but not materially moreso in Haskell.
- kpmah 13y agoAnecdotally, as somebody who has tried to find a Haskell job, there seems to be a LOT of competition for them.
- sanderjd 13y agoIt's interesting, I meant my question to be about finding companies using Haskell or willing to hire contractors that build projects for them using it, but of course I did take it in the hiring direction, so it's my own fault. Rephrasing: Is it easy to find work using Haskell, despite the perception (whether deserved or no) that it is difficult to hire for, which may limit the number of companies willing to build in it?
- deleted 13y ago[deleted]
- leorocky 13y agoMy least favorite example of when tests damage an API design is with dependency injection. There seems to be very little need to resort to dependency injection as a way to create a good API with easy to understand architecture many times, but dependency injection gets abused because it makes testing easier. You can supply your API with mock and stub classes at every turn if you use dependency injection everywhere, but the consequence is a more difficult to use API that requires the programmer who uses your API to understand more arbitrary and unnecessary implementation specific details. For example, maybe I just want to open up an encrypted TLS TCP socket to a server. From a user perspective this could be really basic, you provide a library with an API that you provide with server address, port and handlers. It could be as simple as a few lines of code. But the dependency injection version of this would require maybe require creating an SSL Factory, which requires a 509x certificate provider, which requires a certificate storage locater. Then instead of an address you must provide it with an ipaddress factory method and a protocol factory which requires a list of available protocol implementors. Then 200 lines later you want to actually manage your connection and you must provide a connection manager and a byte buffer which itself involves tons of cruft. Sometimes dependency injection is like a person walking around with their organs hanging outside of the body. When two people want to make babies they don't have to know low level biological mechanics of how sperm sends signals to a ready to be fertilized egg. They don't have to read and learn pointless documentation. They just insert the thing and everything usually works although under the hood it is maybe one of the most complicated processes in biology. That's how an API should work: making complicated things simple.
- WickyNilliams 13y agoCould you not have the best of both worlds by writing a facade over the public-facing API? I have used this approach in the past and it works well. The facade just handles grunt work of wiring the myriad of types together, as you described. It can be verified by blackbox testing or simply by eye (as it should not contain any logic beyond instantiation).
- TylerE 13y agoWhile there are valid use cases, this sort of thinking is why apps are no more responsive today than they were 20 years ago... everything is running through 20 translation layers.
- vinceguidry 13y agoI was unaware that people were actually trying to unit test controllers. That to me just seems like a recipe for endless frustration. Mock out a web request? Please don't. Everything I've ever read about Rails refactoring indicates that your controllers should be skinny, implying they don't need to be tested, push all complex logic out into helper functions, lib classes or models and unit test those.
- yesiamyourdad 13y agoYeah it's a tempting thing to do because there's this naive belief that you must have 100% coverage. The fact that you've got coverage on trivial code is not doing anything to increase your reliability, and you're spending so much time mocking the requests which is not code you have any control over. Your 2nd paragraph is what DHH is fighting against. He's advocating much simpler controllers than many people tend to write, and the the inclination to write tests for a controller is increased by the complexity you've put in there. DHH is more importantly advocating an architectural style and that's getting lost in the "TDD is dead" linkbait.
- jboggan 13y agoI found myself in that exact situation recently, struggling with finer points of Capybara, trying to nail down critical behavior in my controllers . . . then I realized, why had all of this crap crept into my controller in the first place? I refactored a ton of things into the models where they really belonged and ended up with much tighter code and better reuse of functions. Now testing is way easier - the only thing my controllers are really doing now is routing after certain conditions and serving up error/success messages. Sometimes I feel like Steve Martin when I'm getting more sophisticated with a framework . . . I've got a googlephonic stereo with a moonrock needle, but maybe the problem is the shocks in my car: https://www.youtube.com/watch?v=Cjjsz14hL48 https://www.youtube.com/watch?v=Cjjsz14hL48
- ajmurmann 13y agoI don't understand why he has to equal TDD with the mockist approach to TDD without clarifying he is talking about the mockist approach and not TDD in general. Pivotal Labs for example is obviously a huge proponent of TDD, but has been historically been opposed to hesitant towards true, isolated, heavily stubbed and mocked unit tests. That makes me wonder if he just doesn't have a differentiated enough view of TDD or if he omitted that on purpose to get more attention. I am also not sure which answer is would be more disappointing.
- michaelmior 13y agoCurious why DHH is in the title. This doesn't seem to happen with posts from others well-known in the tech community.
- dang 13y agoWe removed it. In general, HN tries to emphasize content, not personalities.
- michaelmior 13y agoThanks Daniel!
- Jtsummers 13y agoBecause it's in the title on the site, however it should be removed and treated like a "- NY Times" suffix. Just checked, it is now removed here.
- michaelmior 13y agoAh, didn't notice this was on the title on the original site. Thanks for pointing that out.
- programminggeek 13y agoI feel like MVC is being treated as like the one true pattern to design your web app with and that's just simply not true. Rails is MVC and maybe that's all it should be for what it is intended for. Other projects might not be a great fit for such a simplistic view of the world and maybe that means Rails is not a great fit for projects that don't fit into the MVC abstraction. In my experience MVP, MVVM, super thin Sinatra API's, hexagonal architecture, functional programming, and other sort of weird approaches fit certain projects much better than the standard Rails MVC approach. Also, not every project is a web app and there are plenty of times where various testing approaches make a lot more sense than they do in Rails. It's too bad that a whole line of thinking about software quality is being disparaged because it isn't a good fit for Rails as DHH sees it. TDD is a useful tool in the right context. Maybe that context isn't Rails. It seems unwise to be telling a lot of smart people who care about software quality to "get off my lawn" so to speak, but I've never run a successful OSS project as big as Rails, so I probably don't have a clue about how to lead a community as big as Rails is.
- ryanbrunner 13y agoI think one important thing to keep in mind is that TDD is not necessarily synonymous with "software quality". In some cases it's a very useful tool to ensure the quality of your code, but it's not even the stated goal of TDD, and a focus on TDD as the "one true path" to software quality ignores that some things are more effective (not necessarily simpler) to test using more of a "test later", integration-focused testing approach. I agree that there's projects where a simplistic MVC approach doesn't completely fit. That doesn't mean that every software project needs to be built to the standards of the most complex software, or even that aspects of a project that do require this complexity can't be solved with a more straightforward, simple MVC approach. At the end of the day, I think the main message I get from DHH's recent series of blog posts is that treating anything as a silver bullet, or a universally beneficial pattern is harmful - and this is equally as applicable to MVC for everything itself as it is for a complex, hexagonal architecture.
- cwbrandsma 13y agoBad programmers will write bad code no matter the methodology, pattern, language, tooling, or best practice.
- darrencauthon 13y agoDoes anyone know of a public example of a Rails application that does testing in the way that DHH says is good? I'm tired of the talk talk talk talk talk talk of "proper" testing in Rails, yet the examples always seem to be hidden away behind company firewalls. I've only seen a couple Rails apps with Rails-Way test suites, and they were nightmares that took many minutes to run. But I have seen dozens of Rails apps written by opinionated Rails devs with strong views about what proper testing was... and the apps had no tests at all.
- techdragon 13y agoIf your tests take 10 seconds, how much did you really test? The point it sounds like he's trying to make is that if you say things like "they were nightmares that too many minutes to run" you may be approaching testing from the wrong point of view. He sounds like he wants to say "let the tests take 5 minutes" and I agree with him, thats what CI is for. Commit your code, mark the issue your fixing, let CI tell you if its done or not, take your pomodoro break, coffee break, etc, then sit back down, and pick back up with your test results on the CI server and repeat the cycle, a 5 minute test suite is NOT A BAD THING... If you think 5 minutes is terribly long spare a thought for us deployment engineers... my test suite involves building and tearing down entire VM's or PXE booted machines and depending on what software is being built and tested through deployment can take an hour or more.
- awj 13y ago> If your tests take 10 seconds, how much did you really test? I see your point, but time-to-test is a horrible proxy for quality of tests. Business logic isolated from external systems can run incredibly fast, so ten seconds worth of testing can mean an awful lot in that case. The nature of TDD basically demands that you structure your code that way to remain productive. Otherwise it's like using a text editor that takes five minutes every time you try to save a file. That's my inherent frustration with this argument. Both sides aren't arguing for their methodologies, they're arguing against the byproducts of each others methodologies.
- 13y ago
- agentultra 13y agoDHH is arguing from the perspective of a Rails developer working on a Rails application. It's no small kingdom but to discredit TDD as a practice for all software developers is short-sighted. There are enough counter-examples of the benefits of TDD in my own experience to make the claim invalid as a universal truth.
- blazespin 13y agoDecoupling is one of the fundamental tenants of development, and provides far far more benefits than merely TDD. If he thinks decoupling is about TDD he's missed out on architectures that can be easily fixed when bugs show up by isolating causes in changed code, being able to extend without modifying core code (the open close rule) and managing regressions in general. How do you scale software to a team of developers without decoupling? The only argument I've ever seen against decoupling is performance, and it's rare that argument makes sense in all but the most real time of applications.
- brianmcc 13y ago(Tenets, not tenants. Friendly heads up, not a dig)
- ebiester 13y agoI don't think that DHH is arguing against decoupling but rather he is arguing that certain decoupling practices that are encouraged by TDD interfere with the readibility of the application.
- dlisboa 13y agoI've never seen a Rails application that suffered from too much decoupling. If someone out there wrote one of those, call me because I have a job for you. 99% of all things written in Rails are monolithic messes of business logic encrusted with persistence and response handling. So ridiculously coupled that you don't talk to objects by themselves, but bring in a whole family of resources to models and controllers that violate almost every SOLID principle. The whole "You're not gonna need it" argument works until you actually do need it. Which, unless you're not doing a good job, is going to happen. Then you have no discriminated interface to pry your application blocks away from each other and can't persist a model without dozens of unintended side effects. There's no readability penalty to decoupling. The more you decouple the less you need to read to understand an application.
- platz 13y agoThis may be a naive comment, but is DHH simply being defensive about a perceived movement away from Rails i.e. "decoupling from Rails"? * edit - of course, he could be defensive and right, they aren't exclusive
- ranit 13y ago>> … it's a mistake to try to unit test controllers in Rails (or similar MVC setups). The purpose of the controller is to integrate the requests from the user with the response from the models within the context of session. Well said … Now, if somebody from Salesforce.com could understand this and stop forcing their customers to write these useless tests for the controllers.
- deleted 13y ago[deleted]
- ascendantlogic 13y agoThe overriding message here is one of pragmatism. TDD, like a lot of methodologies before it, became gospel and people began practicing it in a dogmatic fashion without thinking about the best way to apply the principles to whatever problem is at hand. The spirit of TDD is that you have a safety net of tests to protect you from making changes in class A and breaking something in class B. If those are acceptance, integration or unit tests, great. If the code is cleanly organized and readable, great. Don't let zealots on either side of the aisle convince you to do anything beyond what makes sense to solve the problem that is sitting in front of you.
- Toenex 13y agoWell said. Methodologies should be treated as patterns and as such you need to understand what they aim to achieve, the trade-offs in terms of risks and benefits and most importantly how to adapt the pattern to what you are doing. You open yourself up to problems when methodologies and patterns are adopted without thought.
- conanbatt 13y agoTDD is supposed to affect design in good AND bad ways. It is not true that TDD claims to have the best design, but the more testable. The first time I read about TDD it basically said testability > clarity. A succinct code that you dont know if its doing the right thing is worse than more verbose code that you can easily verify what it does. I do think that specifically with Rails, tests get so plentiful that they take long to run and it threatens the whole process. And the weight of testing models/controllers/integration is something that has bitten me before. Particularly, doing less integration and more model, because integration tests can be flimsy and slow an order of magnitude more. Since my first web programmer job, in 100% of my projects tests grew so big it took them minutes to run, making me nostalgic about the speed of Java tests I had for my first programming job.
- marcosdumay 13y ago> The first time I read about TDD it basically said testability > clarity. Now you scared me. I never tried TDD, and if that's a required tenet, I never will. This is completely upside-down. Tests can not verify that a program is correct.
- conanbatt 13y agoOnly proof can verify a program is correct, and that is so cumbersome and expensive is done on rare critical operations. Tests give you the ability to know how a certain code behaves in specific circumstances. Clarity makes it easy to understand the general case. So if the clarity of the general case is a little worse to be able to test the outlier cases as well, the TDD philosophy would welcome that trade. Or at least, thats how I understood it. A brute and unpolished example of clarity vs testability: A) def complicated_algorithm(input) mod_input = Math.root(input) mod_input = input / mod_input ... end def division_in_complicated_algorithm_test input = 1 input.should_receive(:/).and_raise(ZeroDivisonError) Math.root.should_receive(input).and_return(0) complicated_algorithm(1) end ----------------- B) def complicated_algorithm_testable_version(input) mod_input = Math.root(input) mod_input = divide(input, mod_input) ... end def divide(a,b) a/b end def divide_test assert_exception ZeroDivisionError, divide(1,0) end Overall, the point of doing tests first is that you dont hack them together doing complex dependency injections, because naturally to save work you do the easy test..at some expense of the final code. In the above example, the top example is less verbose, having one full less method, but the test is more complex and fickle, because it was done to verify the above code. Its not a fantastic example, we could argue that the tests try different things, and that division is too silly to put into a different method. The point is that the above happens when you write code first , test later, and the bottom one happens the other way around. TDD advocates for testability over clarity.
- jbb555 13y agoTest... good. TDD ... sometimes (often) good. But unit tests can lead to an overly abstracted design that harms the quality of the code. Test what you can with unit tests but don't compromise your code to do so when there are other ways to achieve a suitable level of testing
- vjeux 13y agoKent Beck response: https://www.facebook.com/notes/kent-beck/rip-tdd/750840194948847 https://www.facebook.com/notes/kent-beck/rip-tdd/75084019494...
- jshen 13y agoThat was a terribly thought through reply. It sounds like the sort of bullshit you hear on sunday morning political shows. "Let's pretend I didn't hear my opponents points, and throw back my own talking points which fail to address his points.".
- jrockway 13y agoOne thing I like about Java programmers is that they realize everything a class depends on needs to be passed to that class's constructor. There's really no way to avoid it. Change concrete instances to interfaces, and you have a nice testable class. Write an integration tests, write a unit test, they're both easy.
- sanityinc 13y agos/Java programmers/good Java programmers/.
- Jtsummers 13y agoCan all this be summed up with "Avoid all dogma, even this."? Any time we buy into a dogma at the expense of rationality, we lose. This has been demonstrated throughout history in human interactions with each other (via religion, politics, legal systems), the development of science and technology (see Galileo, Copernicus, the 19th century US doctors ignoring germ theory and killing a president). Sometimes we create dogmas to try and move things away from bad ideas towards better ideas. Dijkstra's "Go To Considered Harmful" was one such effort. Gotos, as used at the time, were fucking terrible. They were used instead of higher level expressions like if/then/else, for, do/while, function calls. But the (at the time I was in college, early 2000s) refrain was tired and wrong (or misapplied). Sometimes, in some languages gotos can, in fact, be very useful, so long as their use is chosen deliberately and with care (see the C idiom of using gotos to jump to error handling/reporting code in functions). In the end, nearly every development process runs the risk of becoming a dogma. Avoid that. Study the process, practice the process, and reason about where the process should actually be applied. And we already know that the answer isn't "everywhere and everytime".
- briantakita 13y agoBlack box (functional) testing is the way to go. I created a flow style of testing, which allows "Fast & Thorough Testing". This is a javascript & jasmine extension, but the concept can be applied to other languages. http://briantakita.com/articles/fast-and-thorough-testing-with-jasmine-flow/ http://briantakita.com/articles/fast-and-thorough-testing-wi... The nice thing is the testing does not have a large effect on the implementation, so you have the freedom to change the implementation without the tests failing. The test suite scales since edge cases can be grouped together into a single flow. This removes extraneous runtime burden of having to recreate the same context for a each individual edge case. I find that I don't need to be performing TDD as often.
- smrtinsert 13y agoI wonder if this isn't a maintainability issue in disguise. I have never had a problem with unit tests or int tests. As a rule I never use mocks, and everything fits into one of those areas. Either you have real data sources available (such as an in process db) or you make it a module that can be easily unit tested. It's clear he is against TDD first, and looking for reasons second. I feel other factors are at play.
- mattgreenrocks 13y agoMaybe the real problem is that we have crappy tools for hexagonal-oriented architectures; especially Rails. Classic Rails style dictates that ActiveRecord is Good Enough for your domain logic. This creates a sort of framework lock-in: inheritance is one of the strongest forms of coupling there is, especially when you inherit from classes you do not control. The framework superclass is a likely to be a relic of current-gen frameworks that we do not tolerate in the future. The technological way out is to use a Data Mapper pattern ORM to isolate the domain logic and the persistence. But this approach won't catch on, because Rails devs have tasted the simplicity of ActiveRecord and aren't about to do more work to get the same result. It is telling that many language communities eventually head towards amalgamating a collection of really good libraries in a low-coupling manner. This is still a fringe movement in Ruby, currently.
- ryanbrunner 13y agoIf ActiveRecord allows you to do less work for the same result, at least for some less complicated applications, isn't that a good thing? I think that's DHH's whole point - we shouldn't be pursuing some mythical perfectly testable architecture where it doesn't make sense. If you can write clearer, more concise code for less effort that doesn't fit into the purely separated, easily testable TDD approach, is that really such a bad thing?
- revscat 13y agoIn Java, the most obvious example of testing affecting the design of a class is the necessity of avoiding private methods in order to facilitate testing. While there are ways around this -- reflection, PowerMock, probably others -- they all tend to be ugly and hackish. This has an effect upon the design of classes, because the easiest path is simply to make private methods package private. This is frequently not the ideal design, and taken to its logical extreme means that you will have no private methods. I think unit testing is important, and do use it. The line for me, though, is similar to DHH's here: when the drive for unit testing affects the design of the software, that's when I tend to become less enamored.
- j_baker 13y agoI don't think you need to avoid private methods in Java. Rather, I think you should ask yourself if a private method is really necessary. Secondly, you should be asking yourself how you can test private code via the class's public methods.
- yesiamyourdad 13y agoAgree with that. If you want to unit test something, you should be able to test the outputs for a given set of inputs, plus possibly verify some interactions, i.e. if I call chargeCustomersCreditCard(BillingInfo billing, Invoice invoice), then there will be exactly one call to myCreditCardProcessingMock.chargeTheCard(String creditCardNumber, Decimal amount), where creditCardNumber == billing.creditCardNumber and amount == invoice.totalAmount.
- twistedpair 13y ago> I don't think you need to avoid private methods in Java. I think the reverse question is better in Java. Does this method/class need to be public? Only expose the bare minimum in the API so that you retain free reign within your codebase.
- matwood 13y agoTests should be testing a classes public interface, not the private methods that may or may not drive the public interface. What this type of design will push is to stick with the Single Responsibility Principle. If you find yourself wanting to test a lot of individual private methods you have likely violated SRP.
- j_baker 13y agoI don't really see DHH giving any arguments as to why designing for tests leads to poor design decisions. I suppose I can buy the argument that there are cases where this isn't true, but I can't think of any and he's not giving any. I would argue that Angular is a good example of how designing for testability creates good design decisions. Secondly, I don't buy the idea that you should focus on integration tests over unit tests. Integration tests are important, but they're also the most expensive tests in terms of maintenance. Unit tests you can run with every code submit. You can run them multiple times per code submit. Integration tests take too much time for this to be practical. In all, I'm tired of people making decisions based on what they're against. DHH is just being negativistic and defining his code design strategy around being against TDD and test-driven design. That's ok. But what design strategies does he support? He starts giving more information about that at the end, but I'm still left scratching my head and wondering what design philosophy he's actually advocating rather than what design philosophy he's bashing.
- deleted 13y ago[deleted]
- romaniv 13y agoI don't really see DHH giving any arguments as to why designing for tests leads to poor design decisions. It results in pointless levels of abstraction that aren't used to abstract anything in real code, but destroy readability and screw up static analysis tools. It also results in over-splitting of entities to the point where they don't represent anything remotely similar to problem domain. Finally, it encourages "old stuff plus this addition" kind of design. (For example, using a switch statement to cover 7 different cases for days of the week, rather than using a math formula.)
- vendakka 13y agoI certainly do agree about integration tests being important. I've also started moving towards using a live database for my tests. I set up a postgres database by copying over a master copy to a temporary directory and running a postgres daemon from there. It takes ~100ms and with fsync turned off it makes for snappy tests. If it starts getting to be slow I can always move it to a ramdisk. Here's a library I wrote for golang which wraps it all up in a convenient package: https://github.com/surullabs/ghostgres https://github.com/surullabs/ghostgres
- emsy 13y agoI would have a repository layer even if I wouldn't write any tests. I don't want an ORM to interfere with my "business". So this could as well be a post against ORMs. But still I think author is right. TDD does enforce an architecture, and that architecture doesn't have to be the best. TDD zealots often run around, preaching their cult and blind out any drawbacks, as if there were none.