17 ms·
An unexpected benefit of unit tests
- makstaks 4y agoI wish HN had a central thread feature for discussing oft-debated topics like TDD, microservices, and agile. This way, we could contribute to the conversation instead of constantly rehashing the same points through new posts.
- Learner100 4y ago> While not traditional TDD or likely not a new concept, I’ve done something I’ve dubbed TLD (Test Led Development). Rather than writing a whole smattering of tests and then coding until they all pass, TLD focuses on ping-ponging between test and code. TDD commonly gets mischaracterized as a two-step process of writing a suite of tests upfront, then writing some implementation to make them all pass. It's actually much closer to what is described as TLD in the post. You write a failing test, do the bare minimum to make it pass, refactor if appropriate and start the cycle again. It's a development process that will (in theory) produce a high quality implementation and suite of tests at the end.
- Jtsummers 4y agoI was going to say the same thing. His characterization of TDD as "writing a whole smattering of tests and then coding until they all pass" suggests he's gotten some bad information about TDD. Sadly common, though. His TLD is pretty much TDD except he doesn't mention refactoring after passing the tests. Even leaving a failing test at the end of the day as a kind of "todo" for the next day, I'm pretty sure Beck mentioned using that idea in his TDD book. EDIT: Found it finally. From Beck's Test-Driven Development by Example (page unknown, ebook copy, chapter 27): > How do you leave a programming session when you're programming alone? Leave the last test broken. > Richard Gabriel taught me the trick of finishing a writing session in midsentence. When you sit back down, you look at the half-sentence and you have to figure out what you were thinking when you wrote it. Once you have the thought thread back, you finish the sentence and continue. Without the urge to finish the sentence, you can spend many minutes first sniffing around for what to work on next, then trying to remember your mental state, then finally getting back to typing. > I tried the analogous technique for my solo projects, and I really like the effect. Finish a solo session by writing a test case and running it to be sure it doesn't pass. When you come back to the code, you then have an obvious place to start. You have an obvious, concrete bookmark to help you remember what you were thinking; and making that test work should be quick work, so you'll quickly get your feet back on that victory road. > I thought it would bother me to have a test broken overnight. It doesn't, I think because I know that the program isn't finished. A broken test doesn't make the program any less finished, it just makes the status of the program manifest. The ability to pick up a thread of development quickly after weeks of hiatus is worth that little twinge of walking away from a red bar.
- alasdair_ 4y agoBeck had this process (test, code, refactor) in place in his original eXtreme Programming book, which helped spawn the whole “agile” movement. It’s kind of cool to see how the process was refined over time. It’s also sad to see how twisted people’s idea of both agile and tdd have become, usually because they never read the source material.
- switchbak 4y agoHaving been involved in that world from pretty early on, I must admit I'm pretty appalled at the odd interpretations I get both on HN an in real life. I'm not sure where to put the blame, but "Agile" does get a particularly bad rap these days. Mostly I'd say a corporate watering down of Agile concepts, money hungry consultants who didn't know much, and I h ave a personal dislike of how most of SCRUM tends to be implemented. TDD in particular has fallen off the map in a way that I find very surprising. Generational amnesia I suppose.
- mpweiher 4y ago> corporate watering down of Agile concepts I think inverting agile concepts is more accurate.
- Jtsummers 4y agoIt's very common. When I was working at a USAF base they decided to adopt Lean. What they actually implemented was almost the exact opposite of Lean in every way, almost comically so. A key element of Lean is empowering the workers to improve the processes. Let them come up with ideas that improve things and run experiments (guided by management perhaps, but not directed by). But the way USAF did it, the managers would watch a process being done, identify "wasted" movement, and then rewrite the process/procedures to eliminate that wasted movement. It was clearly just Scientific Management but being called Lean because they "leaned out" the processes. Naturally, the actual workers did not like coming in every other week and having to learn their job all over again. After a while they still held "Lean Events" but by then it was for show rather than to actually effect change in how things were done.
- tsuujin 4y agoThere is an awful lot of discussion out there about TDD that does not mention the Red-Green-Refactor concept at all. I think that really changes the impression of TDD.
- eyelidlessness 4y agoIt certainly did for me. A former teammate did a lunchtime session on TDD, which introduced me to the concept. I’m self-taught, and my foray into testing was very much a matter of trying to bolt tests on after the fact. So this concept of red-green-refactor was wild to me. A little intimidating at first, and I didn’t adopt it right away. But when I did, it not only made testing better, it made my code better too. Not only because it’s more testable, but because it makes me think about the interface first, and the implementation truly as a black box as much as possible.
- jpatt 4y agoOne of the most disappointing parts of moving away from .NET for work was the loss of NCrunch. That one tool made Red-Green-Refactor a breeze. In Ruby/Java it is certainly a bit more of a chore to remember to do.
- sixstringtheory 4y agoFirst time I've ever heard of that tool, it looks incredible. I wish Xcode had some of that functionality. It's close in some ways, all the building blocks are there.
- sn9 4y agoHoly shit. Link for the lazy: https://www.ncrunch.net/ https://www.ncrunch.net/
- tester756 4y agoI don't see benefits of R->G step
- 4y ago
- beached_whale 4y agoCombine this with usage testing. At least for libraries, which I seem to end up writing a lot of, writing usage tests that don’t necessarily test units but functionality. Doing this early on helps flesh out the friction in usage and will help with testing as it often can have broad coverage. It also, now sits as a potential example for usage in documentation.
- pydry 4y agoThese are way, way more useful than unit tests IME.
- JoeNr76 4y agoEven after all these years, people still have this misconception about TTD. No idea where it came from.
- nonethewiser 4y agoDogmatic evangalizers
- jdlshore 4y agoMicrosoft. It came from Microsoft. https://www.jamesshore.com/v2/blog/2005/microsoft-gets-tdd-completely-wrong https://www.jamesshore.com/v2/blog/2005/microsoft-gets-tdd-c... (Well, they popularized it.)
- lamontcg 4y agoI'm not sure if I go even further or not, but I'm perfectly happy to write code first and then write tests. And I will sometimes not write tests until I start debugging code and start thinking to myself "is it a buggy bit of this function I haven't tested yet?" and so I'll write the test to prove it isn't that bit. With code I'm getting paid for I'll write more tests up front and won't skip that step, but for code I'm playing with then as long as I'm just enjoying myself writing code I'll write zero tests for awhile and just code. Then as I hit the debugging/refactoring step I'll do the backfilling as a form of debugging. Often I'll write the code that fixes the bug, then write the test, then quickly and temporarily revert just the code to ensure that the tests fail and then proceed. That gets you the same safety check as doing it the test-driven way to validate your test actually tested the right thing. I really tend to hate "thou shalt start by writing tests" as some kind of immutable golden rule. It does always feel good to me when I just naturally wind up doing it, but forcing myself to do it every single time just isn't any fun at all. At the same time tests are absolutely essential when it comes to refactoring and debugging. When you get too far out ahead of yourself with code then you start needing to shore up the foundations and use tests to eliminate bugs in the code that you've already written. Some code though is obviously correct enough that in personal hobby projects I won't ever code tests for them (unless I do hit the point where I start to doubt their correctness due to some funny bug at which point the situation has changed so I add some tests to prove it one way or the other). The whole point of this though is that the tests are always serving me and they aren't in the drivers seat quite the way that all the TDD 101 blog posts like to ram down your throat and which I suspect turns people off from that approach so much. The end result is also that you'll tend to wind up with the tests that are actually useful, covering the code that is particularly hairy or essential and the edge conditions that you really need to make sure to get correct, and you wind up having a test suite which is composed mostly of useful tests instead of all the largely useless ones that infect codebases. I'll also happily omit tests on lower level functions that are well tested at the level above them, because I don't need to test the same thing at 18 different levels (again, for professional use I'm more likely to include tests at every level if they're fairly mechanical to produce). I also have a flexible definition of what the system under test is, which often encompasses more than just the immediate object that I'm testing and I don't bother wasting mental effort thinking about how to mock the whole world. I don't know what kind of TXX that is. I still wind up with tests, they're legitimately essential to have, I just don't get there via some prescriptive route. I wind up with good code coverage, but I don't necessarily wind up with it looking as comprehensive as rotely banging out lots of unit test. I typically wind up with tests that I know are useful because they were produced by hitting actual bugs or where I had real questions about the behavior of the code and needed to assert some invariants and prove the code worked.
- danielvaughn 4y agoAlso, as with almost everything else in life, it's important to not be so dogmatic about the "rules". Everything is negotiable - I'd never recommend literally only ever follow the strict red-green-refactor workflow.
- garethrowlands 4y agoI recommend people learn the “proper” technique, even if they don’t apply it in every context. As they say, _you need to be in the mould to break the mould_.
- User23 4y agoTDD properly done is a 4 step iterative process. 1) Write test. 2) Run test and see that it fails. If it doesn't fail figure out why and fix it so it does. Either the test is wrong, or what you're testing is trivial. 3) Write code so test passes. 4) Refactor code so it's good. Next go back to step 1. Step 4 is the trickiest and most important part. Refactoring transformations must be such that they do not invalidate the results of step 3. But if you don't execute step 4 properly you'll end up with crap code.
- hbrn 4y ago1. 90% of the time what happens during step 4 is realization that your tests are crap. So you have to throw them away and start from scratch. Tests are not helping with refactoring, they are inhibiting it. 2. Step 4 is indeed the most important part, and yet TDD priests and scriptures don't cover it at all. TDD is actually distracting you from what's important, because it focuses on steps 1,2,3. Eventually you'll become disciplined enough to not get distracted, and you'll think that TDD works. But the reality is that you never needed TDD in the first place.
- User23 4y agoI’ve used TDD as a kind of proxy for predicate transformer semantics. Test first is a way of sneaking specification first into a team’s workflow. But let’s be clear, overspecification is bad specification! And that’s why as you say a lot of times the tests get in the way: they are overly specific and thus inhibit desirable refactoring. In my experience it’s better to err on the side of underspecifying. Most everyone agrees with me on that, since the total lack of specification you generally see is a species of extreme underspecification. Don’t think I’m being snarky either, even that level of underspecification can be appropriate in the exploratory phase, although I think it’s generally worth the effort to start from a specification that’s at least slightly more restrictive than the always true predicate.
- hbrn 4y agoYeah, that's how I think about testing in general. Tests should be a reflection of your specs. But if your specs are bad (most of them are), you should wait for them to improve. Not carve them in stone.
- nine_k 4y agoTwo-step TDD can work if you implement a well-known, stable spec, say, an IP stack. Such cases are relatively few.
- DougMerritt 4y agoRight, and in the absence of a well-known stable spec, then it seems to me that TDD (and related) is just a variant on the very old and very discredited "Waterfall Model" [0] [0] https://en.wikipedia.org/wiki/Waterfall_model https://en.wikipedia.org/wiki/Waterfall_model
- Jtsummers 4y agoHardly, if it was a variant of Waterfall you wouldn't have testing mixed in with the development of new code. Waterfall has explicit barriers between the development and test phases which is why those systems usually turn out to be clusterfucks (unless they're small or you have, usually by luck, a correct specification). In real-world Waterfall projects (which I have suffered through, worst was a multi-billion dollar disaster for tax payers, and a multi-billion dollar success for the contractors that we took it over from), testing happens after development which means you have no useful feedback while developing that you are building the wrong thing or building it incorrectly.
- DougMerritt 4y agoI had in mind that Waterfall separates architecture design from detailed design from implementation, and in the above case, the spec being precise and finished certainly means that the top level design (or architecture or whatever) is 100% finished before the TDD development begins. Hardly a reason to downvote me.
- Jtsummers 4y agoBut TDD does not separate architecture design from detailed design from development from testing like Waterfall. It integrates them, or at least enables integrating them. It is literally not Waterfall, which is an idealized (and thus its primary flaw) process predicated on that strong separation that TDD deliberately breaks. TDD is predicated, instead, on the idea that we don't know everything up front. Otherwise we wouldn't need or want to write tests in the middle of development. As to the downvote, I guess you thought it was me, it was not. But I don't plan to upvote you either.
- heywhatupboys 4y agoit is just the classic arrogant blogger isn't it? "I rediscovered something obvious, mischaracterised what exists already, and I make up a term and pretend like it is novel, and credit myself with it" Hoping someday to be the new Fowlers, surely.
- finnh 4y agoYou're getting downvotes for snark but you're not wrong.
- heywhatupboys 4y agothe truth is ill heard lol
- brightball 4y agoYep, agreed. I tend to just code a single giant integration test where I map out the happy path to start with, and then when it works code tests for known edge cases at specific parts. Keeps me focused.
- sorokod 4y agoIt's a development process that will (in theory) produce a high quality implementation and suite of tests at the end. In fact the theory is that this approach will produce a high quality design as an emergent property. This is an extraordinary theory that requires an extraordinary proof - one I haven't seen so far.
- jdlshore 4y agoIt's fairly straightforward: TDD is red/green/refactor, which means you're working in very small steps (about a minute or two each) and thinking about design during two out of three of those steps. During "red", you're thinking about the design of your public interface. During "green," you're focusing on implementation. During "refactor," you're thinking about how to improve the quality of your implementation and how to improve the overall design, and making those changes. If you believe that spending a lot of time thinking about and improving your design will produce a high-quality design, then TDD will produce a high-quality design. QED. If you don't accept the axiom, then it's a longer discussion, but that's the proof, and my experience is that it does in fact work. (If you're looking for a rigorous study and proof, you won't find it, because there are no rigorous studies that formally prove what creates high-quality design. Partially because there is no formal definition of "high-quality design" in the first place.)
- sorokod 4y agoTDD is obviously a hill climbing strategy as far as the end result is concerned and the outcome has the associated baggage. "Refactor" is named this way to emphasize that changes you are making are closely related to the tests you already have and the tests you are about to introduce. That does not leave enough space to justify a QED. If you choose to design beyond that, the process stops being TDD - at least as described by Kent Beck
- hbrn 4y agoTDD has several big issues that lead to bad design: 1. It assumes your spec is good and rigid. In reality, most specs are shitty and fluid. And your first understanding of spec is wrong. 2. It assumes your first implementation of the spec is good enough to justify automated testing. 3. It leads to high test coverage which inhibits refactoring (despite zealots telling you otherwise). 4. Almost always it leads to obsession with testing, which leads to a ton of unnecessary complexity (e.g. dependency injection for the sake of testing, weird practices like "don't mock what you don't own", etc) I always thought it's great that it forces you to think about public interface, but I came to believe that thinking cannot be forced with a ritual.
- sedatk 4y agoTDD may not be a two-step process, but its first step is always "write tests" hence the term test-driven, which author clearly diverged from.
- superjan 4y agoI don’t either. But somehow TDD is considered the sanctioned way to work with unit tests, and that it is almost pointless to write tests unless you’re doing TDD. I am relieved to read that I am not the only one.
- garethrowlands 4y agoTDD's first step is write *a test*, not "write tests". Batch size of one. If it was "write tests," it'd be more waterfall.
- sedatk 4y agoStill, you write a test first which, again, the author diverged from.
- lowbloodsugar 4y agoIt always amazes me when someone has the audacity and sheer lack of curiosity to decide that something they've read about means something else, and then "improves it" and "I've done something I've dubbed" to what the real thing is in the first place. TDD has a wikipedia page [1] FFS, which is the #1 hit entering TDD into google, and which very clearly lays out that TDD is a test/code cycle (red, green, refactor anyone?). What the author claims is TDD is called TFD (Test First Development). How do people develop this kind of hubris? [1] https://en.wikipedia.org/wiki/Test-driven_development https://en.wikipedia.org/wiki/Test-driven_development
- zulu-inuoe 4y agoWhat I find so interesting is that we all effectively do TDD, except without commitment to it - Imagine writing some code and not at least testing it out with several test cases. The absolute bare minimum should be for you to write those informal tests down and commit them to the repo. That's some golden knowledge I've gotten over time It just requires the overhead of setting up the test runner
- spc476 4y agoBut the orthodoxy seems to be write the tests first, see them fail, then write code, not write a little bit of code, write some test cases, repeat. How "test first, then code" works in a compiled language like C++ or Rust be beyond me (unless I'm taking this to a literal conclusion).
- thraxil 4y ago> How "test first, then code" works in a compiled language like C++ or Rust be beyond me It's not really any different than in dynamic/interpreted/weakly-typed languages. "Writing the test" for a function sometimes just includes writing a method/function with the appropriate type signature that does nothing (maybe returns a dummy value). Forcing you to view the code you're implementing from the viewpoint of someone calling that code from the very beginning is one of the advantages/goals of TDD. If you find that it's difficult to set up the objects/data you need to write a test, eg, your code has a bunch of implicit dependencies on other components being in a particular state or it takes a ton of arguments that all have to be constructed, that's usually a strong indicator that you should rethink the design. You're getting early feedback on your API design before you waste time implementing it.
- mattm 4y agoSadly, this was my impression of TDD for years. I always thought that writing every single test case up front would never make any sense. It was only when I read the original definition of TDD from Kent Beck that it clicked. I use it quite often now and it makes development a lot easier.
- k__ 4y agoI always think, the next time with tests. Then I have to integrate a monster of a library that needs a whole battery of polyfills, and does it's own thing rendering modals somewhere, and I'm not in the mood anymore. Mocking that whole thing? Hmnothanks.
- xiphias2 4y agoI'm seeing the terrible practice of people writing code bases where a lot of logic depends on the ui framework. I guess your case is something like this as well, that's why it's so hard to test.
- k__ 4y agoTrue story. Problem is, often you get an easy integration route, where lib and UI come in one package or a hard route, where you have to build a UI around the lib. To save time, you use the full package, but then testing becomes a nightmare. And the sad thing is, the stuff that's hard to test is crucial to test.
- xiphias2 4y agoHaha totally agree :) I guess in this case what matters is the estimated lenght of the product lifecycle. I'm right now writing open source library in ts hoping that a ui will pick it up. I may need to write the UI myself at the end, but that will be much easier after I put every feature in my library I can think about :)
- TillE 4y agoDespite the niche obsession with 100% test coverage, tests aren't an all-or-nothing thing. Test what can easily be tested. Architect your code such that business logic, etc is modular and testable. Don't worry too much about testing the hard stuff, especially if it's not likely to break.
- k__ 4y agoBut isn't hard stuff the most ugly one when it breaks without a test? Stuff that's easy to test is usually also easy to debug.
- TillE 4y agoIt's hardly a secret, but it's still not widely understood that one huge advantage of starting with tests is that it forces you to think about how an API will be used. Instead of just writing a class with a bunch of methods that seem useful, you write the tests to figure out what a user actually needs, and iterate on that. And as a wonderful byproduct, you get a suite of regression tests so you can comfortably refactor and add features in the future.
- tgv 4y agoDepends on what you're doing. If you're adding functionality because of a need, you've got a minimal "API" (or rather, interface) already defined. If you ignore that, you might end up with an interface that's not complete and with features that won't be used, but need to be maintained anyway. There's no fast and hard rule, I'm afraid.
- karmakaze 4y agoI often implement top-down, calling non-existent methods that do what I wished they existed to do. Then implement the called functions or even classes. The source should be comprehensible on its own at each level with good naming and separation of concerns. This is a generalization of how to write a recursive function, pretend it exists before you write it.
- mirekrusin 4y agoI write code bottom up. Break problem to first principles, write/reuse primitive, combine them. Fixing bugs is easy as one of underlying things doesn’t work and you can scope it down quickly and patch with test.
- lelanthran 4y agoThe problem with that is that no one, not even you, has 100% accurate foresight. IOW, you will never have your bottom layer done correctly or completely. At some top layer you're going to think "well, looks like I won't be need that function", OR, "Well, looks like I am missing a function for $FOO"/
- mcculley 4y agoIt is said that Hemingway leveraged the same effect by leaving a sentence unfinished when stopping writing for the day. I use the same technique to remind myself I am not finished with something. This is also called the Zeigarnik effect: https://en.wikipedia.org/wiki/Zeigarnik_effect https://en.wikipedia.org/wiki/Zeigarnik_effect
- bbx 4y agoI knew about the Hemingway method but didn’t know there was a psychological name for that effect. It also goes well with the pomodoro technique, because that almost forces you to interrupt your progress while in the middle of something, leaving things unfinished, so they end up easier to pick up later.
- joshcanhelp 4y agoCame here to post this. > The best way is always to stop when you are going good and when you know what will happen next. If you do that every day … you will never be stuck.
- mateusfreira 4y agoAbsolutely it maps one to one with Hemingway, idea. It also works very well for personal projects or projects that you rarely have time to dedicate but want to use any opportunity to make process you can
- aatd86 4y agoI will be a little contrarian maybe but I don't really start with tests. The rationale being that until the API is fixed, I don't want to have to adapt the tests to the changing API all the time.
- bobleeswagger 4y agoI don't get the obsession with TDD. Maybe it's because I have been in the BI/DE space for awhile, but nothing is defined well enough to justify TDD in the first place. Everything is hobbled together and functional integration tests are much more important. Unit tests aren't going to catch when my CF stacks are broken. Good dev tools and architectural design are much more important in my experience. I totally understand where TDD shines. When you have a well defined problem to solve, unit tests are the definition itself, and the business logic therein.
- naasking 4y ago> The rationale being that until the API is fixed, I don't want to have to adapt the tests to the changing API all the time. The rationale behind test-first is that you won't know what the API should look like until you try writing some sample client code that uses your provisional API to access some data. So why not make those tests, iron out the kinks in your API and have a few functional tests at the end of it; two birds, one stone.
- aatd86 4y agoStill makes more sense to go API first then unit tests rather than the reverse to me. Since an API is a composition of multiple parts that can be tested (the unit in unit testing), it only makes sense to create these tests once the relations between these parts/units are well-established. That doesn't have to always be after the API is completely done of course. Some internal functionalities are likely to be immutable. They can be tested quite early in the iteration timeline.
- naasking 4y agoWe're not really talking about unit tests though, we're talking test-first. They don't have to be unit tests, which is why I described writing sample client code because that's what you need to do to anyway to design your API.
- adrianmonk 4y ago> TLD focuses on ping-ponging between test and code. I like this same concept for top-down and bottom-up design. Top down advocates might say if you focus on implementation concerns, you risk building what's easy to build instead of what's needed. Bottom up advocates might say if you focus on what you'd like to have, you risk building something that just can't be implemented well when something else (that can) might have been fine instead. I think, instead, you should ping pong between both. Start at the top, think about the high-level design you want, then work downward and see if it makes sense. Maybe even write some code to try out some ideas. (Like, if I use this design, I'm going to end up needing this database query. Can I make that run efficiently?) Then percolate those lower-level concerns and the results of that research upward and see what adjustments you can make at a top level. Then repeat until you find something that works well at all levels.
- jonathaneunice 4y agoNot quite binary ping-ponging, but I've have often found rotating through writing functional code, writing documentation of what's desired, and writing test cases very helpful in defining "What should each function do? What parameters and context should each take? How should the overall solution be structured?" in cases where that isn't entirely clear (e.g. first pass at solving new or complex problems). Sketching functional code (and/or pseudocode/stub-only functions) lays down a concrete hypothesis about what the code (or at least its key parts) should be. Writing docs fleshes out the aspirations and expectations (including helping you define non-goals and not-yets). Writing tests demands thinking through edge, corner, and other hard cases. Each informs one's understanding and intuition about the other two, and almost automagically drives gap analysis. "Whoops! Haven't thought about/implemented/documented/tested that yet! Let's go do that now! (or at least put it on the TODO list)" It's a strong tight loop. Would also suggest you don't limit your "write tests" phase/work to pure "does just one thing" unit tests. You don't initially want full end-to-end tests that assume and require everything's working in order to start testing, but some of the tests can and probably should venture into the "requires multiple components interacting" space usually called "integration tests." I think of that happy middle ground / hybrid between pure unit and full integration tests as "functional tests" or "functional block tests" where the size/complexity of the functional blocks under test have more leeway than unit testing/TDD dogma usually allows.
- AndyPatterson 4y agoI think the sincerity of this makes this the funniest thing I've read on HN.
- drewcoo 4y agoI like the way that the author literally reinvents TDD while badmouthing "TDD." Which garners lots of commenters not talking about the author's content, but just here to also badmouth TDD. It's possibly the best testing-related Poe/trolling I've seen.
- matthewfcarlson 4y agoYes, this comment thread has correctly pointed out that my understanding of TDD was and still is incomplete. I think there's some aspects of TDD (such as refactoring after implementation) that I'm not interested in. The two folks I know who are big TDD proponents tend to gravitate towards test first development (TFD). In my mind, that was TDD. You write the tests and that drives how you develop the tests. In a recent personal project I started using tests on a bit of a whim and found an unexpected benefit that I wanted to share.
- valenterry 4y agoI don't like the dogmatic test driven development appraoch - so here's mine: --------------------- 1. Use a programming language with an expressive statical type-system 2. Write only type-signatures for everything that I want to do (using type-holes) and continously compile my code 3. Once I'm finished with #2 and it all compiles, I start implementing all the methods/functions 4. If I can't implement a method because I got the types wrong (e.g. my types say that I will return a number, but in some cases I cannot and have to return nothing or an error) then I change the type-signature and go back to #2 5. If I can implement a method but I'm not sure it will do the right thing (despite compiling) without running it manually to see if it works, then I write one or more unit tests for the method. If I believe that changing the method will easily break it I also write a test. Otherwise I write no tests. 6. Once finished I write some integration tests for what I deem necessary - both positive as well as negative tests. 7. Done
- garethrowlands 4y agoSometimes, I find I need to revisit some type signatures after I've begun to write code. And this can lead to my code not type checking for longer than I'd like. But I can still run my tests if I defer type errors to runtime (they still appear at compile time but as warnings). See (Deferring Type Errors to Runtime)[https://downloads.haskell.org/~ghc/9.4.4/docs/users_guide/exts/defer_type_errors.html https://downloads.haskell.org/~ghc/9.4.4/docs/users_guide/ex...]
- Waterluvian 4y agoTerms like “TDD” come loaded with all kinds of varying opinions on what that means and how it should direct your other behaviours. It’s okay to write unit tests first without ever thinking or saying “TDD.” “But that’s TDD!” “I dunno. I just wrote my tests first.” “But you’re doing TDD wrong because you didn’t go and make it minimally pass before improving it!” “I dunno. I just wrote my tests first.” Doesn’t mean it’s not right to adopt a pattern when you identify that you’re implementing something similar to it. But you aren’t obligated to get trapped in its gravity well.
- drewcoo 4y ago> all kinds of varying opinions on what [TDD] means I guess I go by what Kent Beck, who coined the term said TDD meant. https://www.goodreads.com/book/show/387190.Test_Driven_Development https://www.goodreads.com/book/show/387190.Test_Driven_Devel...
- deleted 4y ago[deleted]
- UK-Al05 4y agoSays TDD is silly. Then literally describes text book TDD as his preferred approach? Writing all tests up front has never been TDD.
- matthewfcarlson 4y agoI’ll admit, it was a fundamental flaw in my understanding. The comment section here has given me a few things to think about and I’ll likely add an editors note onto the original article.
- brianpan 4y agoNext up: Fighting Distraction with Pair Programming. I'm a distracted developer, but you'll never guess the unexpected advantage I discovered of pair programming...
- rileymat2 4y agoI am not a TDD evangelist by any means, but if people would read a bit more detail about it, and use it as a set of guidelines we would be better off. For instance, one of the things they say beyond the "red/green/refactor" is "as the tests get more specific, the code should get more generic." This is an interesting concept in itself. Edit: To clarify, if you are writing the least code to make it pass, and do not have the concept that the code needs to get more generic, you /will/ think the whole thing is silly.
- adamwong246 4y agoshameless plug of my side project: https://github.com/adamwong246/testeranto https://github.com/adamwong246/testeranto
- sorokod 4y agoWhile unit test are important for various reasons "The broken unit test stuck out like a sore thumb." is not one of them. In the precise place in code you want to resume work, add in free text whatever mental bookmark you need, the failing compilation will stick out well enough.
- oweiler 4y agoYou can also leave the code you've worked on last broken.
- major505 4y agoBeing honest most companies I worked, unit tests have no use at all. They are just writen because someone determined some arbitraty percentage of code that needs to be covered, and because it takes as much time to write the tests as to write the code, people just write useless tests that test absolutaly nothing usefull, and are worth nothing. And this is not small companies we are talking about. They are banks, big educational institutes, etc. In the end the only thing most managers care is that the litle label in the pipeline dashboard that indicates the test percentage stais green.
- videogreg93 4y agoThank you! I feel like all of this Unit test stuff is just a case of Emperor's New Cloths: everyone writes tests, no one knows why but won't admit it. I've never never ever experienced in my life a time where a major problem was automatically found by some unit tests before it got pushed to production.
- UK-AL 4y agoI have. A lot.
- cwilby 4y agoOur team too, time saved is fairly quantifiable.
- disgruntledphd2 4y agoI mean, you probably need automated CI before this becomes likely as otherwise people just forget to run the tests...
- nonethewiser 4y agoYou absolutely need that.
- RHSeeger 4y agoI tend to describe programming as building a house of cards in your mind. It's a way of thinking about it that's used sometimes to explain why interrupting a developer can be so frustrating (because they have to rebuild the house). Writing code in pieces (functions) where each does one thing, even if that thing isn't reused elsewhere outside of the one place you need it, if one way to reduce the impact of that house of cards; because you only need to focus on the logic that's important "where you are". Unit tests are another way. You can describe the behavior of your code in tests and, if you break something because you couldn't keep the entire model (and individual pieces) in your head at the same time, your tests help you notice that. If you're building your code initially, your tests can help you identify and focus on individual behaviors you need to work. I think the above is why I think both pre-code and post-code tests are useful. It's helpful to write tests before you write the code, to guide your development. But it can also be helpful to write tests after; especially as you identify things that don't work correctly because you didn't realize they were requirements when the code was originally written. They are, effectively, regression tests... but they're more than that, too.
- trevor-e 4y agoEarly on in my career I was overly enthusiastic about writing unit tests for maximum coverage. I wouldn't say they were totally useless, but anecdotally I don't think they caught many bugs/regressions and over time it was a lot of code to maintain. While it made refactoring feel 'safe' to do, it also slowed me down quite a bit. Now, my strategy is to instead focus on writing a few solid end-to-end/integration tests. These tests often find just as many bugs/regressions, are actually testing the entire system, and much easier to maintain. Most bugs happen due to bad interactions between systems. I still write unit tests for some tricky code, it just isn't my first choice.
- john-tells-all 4y agoAfter many years of experience, I agree. Tests are a business investment, of tech resources, to create business value. It's good to focus on the Testing Pyramid [1]. High level tests are slow and brittle, but connect the low-level code to business features. Unit tests are fast and detail oriented. In practice I write 1-2 high level tests (generally end to end, sometimes UI, or API/integration tests) to help focus development and have something that the business understands. Unit tests are helpful to "smooth the path forward" to ensure the code works as expected. Integration tests are great to iterate on, so that new code and tests actually work with real APIs correctly. Tests are not free. However they create a lot of value -- they create (business and tech) confidence that the system is working as expected. Like you mentioned they assist refactoring, which makes the code much cheaper and easier to work with. [1] https://martinfowler.com/articles/practical-test-pyramid.html https://martinfowler.com/articles/practical-test-pyramid.htm... (disclaimer: writing a book about tech feedback loops, e.g. tests)
- nonethewiser 4y agoUncovered code tells you something useful. That there are no assertions made about some code. Covered code tells you nothing. It tells you this code may or may not have assertions made about it. Tracking coverage is good because it shows you what code isnt tested. But once its covered, you have no idea. So instead of increasing coverage, you should be evaluating the new assertions being made to uncovered code. But virtually everywhere I've been has just used coverage going up to mean your tests are sufficient.
- deleted 4y ago[deleted]
- thadt 4y agoI like it. I appreciate someone writing into the void how this helps them focus and be productive in their workflow. Who cares whether it 'fits the technical semantic definition of INSERT_ACRONYM_HERE'. Unit tests are a literal investment - somewhat like buying insurance. Sometimes they're really great - almost necessary - and other times they're not worth the price. The article is offering a point of view of how tests are paying for themselves in an indirect way, which is useful to think about in the cost benefit equation. The position of "insurance is useless" means maybe you just haven't been in a situation where it could really, really save your bacon. Likewise, if your rule of thumb is "buy all the insurance, all the time" - then BestBuy will gladly sell you a $5 'product protection plan' on your $10 purchase.
- matthewfcarlson 4y agoYes, you're getting it exactly. This was for a personal project. The expected audience for it was my spouse and I and writing tests seemed like a nuisance. I don't even know why I started as getting the environment building in a testing harness was actually quite a bit of work. I noticed a benefit I didn't expect and wanted to share. Did I not have a concrete idea of what TDD was when I wrote it? Yes. I had muddled test first development with test driven development as the two people I knew who were big on TDD were focused on TFD as they were refactoring large legacy codebases. I'm still murky on what TDD is (and it sounds like opinions differ) but I don't think I've discovered anything new. Just new to me.
- Jtsummers 4y agoEven if you don't want to read Beck's book on it, TDD is ludicrously simple and still somehow greatly misunderstood: 1. Write a test 2. See that it fails (if it passes, you're testing a feature you already implemented or you're testing something you don't mean to be testing) 3. Make it pass (by changing the code, not the test, seen that too many times) 4. Refactor 5. Repeat The catchphrase is "red-green-refactor". I don't know why people try and make it more complicated than that. Regarding your post, just wanted to say that if you took my other comments as criticism of you they were not. Your misunderstanding of TDD is actually very common and I was intending to address that, specifically. I actual consider the fact that you both rejected the flawed TFD-style (and mass creation of tests that comes with it) and rediscovered TDD (the above process, more or less) both amusing and a positive thing. It shows you're doing what many others aren't, actually thinking and experimenting to discover techniques that work (at least for you and on this project).
- epolanski 4y agoI like TDD myself but I'd rather write E2Es or integration tests.
- Jtsummers 4y agoWhy not both? That's what I do, it's very effective and nothing about one prevents the other.
- iraliaf 4y agoI just started working at a 15-person startup where I am on a small team of 5 devs. People typically "don't have time to write tests" and are encouraged to pump out new shiny features every 2 weeks. I am a new to development but I've noticed that lots of errors pump through code where "No method X defined for nil object" or something like that. I wonder if I could somehow make the case that we'd spend less time reacting to problems and bugs if we spent more time up front writing tests that could give us more than 19% code coverage. Does anyone have advice for starting this conversation with my boss or during one of our standups? I know I could somehow pitch the value as "less problems later for more time spent now" but is there a more effective way to say it?
- golemiprague 4y ago[dead]
- vanjajaja1 4y agoGiven that you're new to development, you'll probably lack the sway to get people onboard with just words. It's likely that the team already knows about testing and the benefits of it (its not a secret, the text is everywhere,) and yet they choose to not do it still. They believe tests will slow them down, probably based on past experience, and using the words of people who are not there on the front lines is unlikely to sway them. To get out of that situation, I'd recommend leading by example and showing how you have personally managed to use tests, in the space they are operating in, to make your life better and develop faster. Once you have something that has provably worked for you, you can start evangelizing it and onboarding other people.
- karussell 4y agoYou could introduce (or write more) tests from your side for new feature that you write or for bugs that appear. But in general it takes a bit more experience to get unit testing into existing code, but not undoable and you could practise this for a smaller, independent or even hobby project. Still, the critical part is to get support, not just acceptance, from your boss as sometimes "less problems later" argument does not convince them as "it always worked well without tests". And just adding tests isn't the only thing it takes to improve the software quality.
- revskill 4y agoFor anyone who hates to write tests, there's another testing strategy that i also found practical and useful, it's called Snapshot Testing. Basically, you can write a script to dump all outputs. And everytime you change the code, just try to compare the old vs new snapshot output.
- candiddevmike 4y agoWonder if you could do this with some kind of deterministic fuzzing
- revskill 4y agoHm, it's new to me. Do you have any guide for this technique ?
- Pxtl 4y agoMy rule is simple: if I want to test something, I have to code it. I don't know if it's full TDD, but if it's worth testing it's worth automating. If there's some edge case where I trust the logic to Just Work, I don't test it at all. The important thing I find that helps with the mental overhead of testing is a long script of integration tests is better than nothing. Too often "TDD" is interpreted as creating perfect isolation of unit tests on each method -- which is fantastic! But is also a lot of work. Worse is better. If just checking in some precondition data and loading it in and then sequentially running a script of automated tests that all have side-effects that could impact the next test is your MVP for a test framework, do that! Having a suite that you have to run fully end-to-end isn't great, but it's better than nothing. When that frustrates you, refactor it into true isolated unit test.
- logicallee 4y agoOne reason for unit tests is to know when you've won. For example ChatGPT is already a limited form of general AI, but makes some silly mistakes for certain kinds of thinking. An example would be - oh wait, it just passed it, nevermind. (I wanted to quote a kind of numerical thinking test it fails at it but it just passed.) If this had been a test previous GPT's failed at, but it was coded up in a unit test, then maybe more people would get it. At some point AI will just pass all the tests we throw at it. It would be nice to have a spreadsheet of tests to 1) know whether we're there 2) show people that we're there.
- btbuildem 4y ago> I’ve always poo-pood the idea of test-driven development (TDD), as it seems wild to me to write a test for something that you don’t know what it needs to do yet Right. If you don't know what it needs to do, why in the HECK would you be writing any code at this point?? If you're just hacking / prototyping, there's no conceivable reason to involve tests.
- tonto 4y agotypescriptifying some legacy code is a similar idle brain task that i've sometimes done
- okamiueru 4y agoThe title of this post doesn't do the author any favours. It can be understood to be directed at the general reader, and I can assure you, that what is described is very much an expected benefit of unit tests. But, it's also just a tool. That has to be used for the right reasons and the right circumstances. One unexpected use for them is to implement the "right way" to do something as a unit test. Instead of having to remind them of what we agreed on, making a test is a great investment, and not the typical use case for tests.
- deleted 4y ago[deleted]
- ChrisMarshallNY 4y agoThis is pretty much how I work. I describe my approach here[0]. I write tests and functionality together, and my tests are frequently harnesses; not "unit tests," as they are understood, today (In "the elder days," we used to call test harnesses "unit tests," but what did we know?). [0] https://littlegreenviper.com/various/testing-harness-vs-unit/ https://littlegreenviper.com/various/testing-harness-vs-unit...
- cirrus3 4y agoI think you just re-discovered what people actually do when they say they do TDD.
- ribs 4y agoAs others have noted, this is basically TDD, and his idea of what TDD is was faulty.
- lumb63 4y agoThis just in: man tried TDD and likes it.
- PebblesRox 4y agoA "good stopping place" is the worst place to stop. This article is a good example of making it easier to get started next time by leaving some loose ends for your future self.
- rognjen 4y ago> TLD (Test Led Development) I've also heard it referred to as "weak TDD"
- SergeAx 4y ago> TDD shines when refactoring a codebase. Treat it as a black box, document and validate the behavior, and start rebuilding the inside. This looks to me like writing tests around code that didn't have any before. It is crucial for refactoring to have tests; moreover, I would say that refactoring is impossible without tests. But it is not TDD, it is just adding tests where it should be in the first place. > when you’re cobbling together a project from scratch, iterating and hacking away until something decent works, writing test code to throw it all away seems like a waste This is exactly what TDD is for: to thoroughly think over the architecture and implement it right from the first take, without throwing away a lot of code. This is exactly the problem of people like me or author of the post: we just love coding as the process. Attention deficit greatly adds to that, making good architecture an impossible feat. It is easier for us to rewrite everything from scratch several times.
- mateusfreira 4y agoI don't think this is an "An unexpected benefit", I do it like for a long time conceit, that idea is based on "The Hemingway bridge" idea from the Nobel prize winner of literature in 1954. I wrote about it a few months ago called it "1. Always go home with a broken build"