666 karma · joined August 11, 2009
Personally, I like my tests to be pretty clearly about the behavior of the contract, and not the implementation, which is hard when you require every method have a test.
I'd also be concerned that other team members are reluctant to delete tests - as this is a dysfunction I see often, and try to counteract with varying degrees of success.
But one detail ran counter to my personal practice.
I don't believe that "symmetrical" unit tests are a worthy goal. I believe in testing units of behavior, whether or not they correspond to a method/class. Symmetry leads to brittleness. I refactor as much as possible into private methods, but I leave my tests (mostly) alone. I generally try to have a decent set of acceptance tests, too.
Ideally, you specify a lot of behavior about your public API, but the details are handled in small private methods that are free to change without affecting your tests.
It's true that nothing forces your to refactor - but I think wanting that is a symptom of treating TDD as a kind of recipe-based prescriptive approach. It is not a reflection of the nature of TDD as a practice or habit.
It's a subtle difference, but important:
A recipe says "do step 3 or your end result will be bad"
A practice says "do step 3 so you get better at doing step 3"
But if the design in your head is an algorithm that can be expressed in natural language, then TDD will let you take that and make a loosely coupled, maintainable program out of it.
Good post on the difference here: http://cumulative-hypotheses.org/2011/08/30/tdd-as-if-you-me...
This is why I have to watch co-workers create dozens of useless error classes and utility functions, and totally ignore YAGNI, before actually taking a single step towards a working program.
If you don't know the algorithm, and haven't figured out the abstractions, then just slinging some implementation code is the easiest (and laziest) thing you can do. It's basically a way of avoiding having the necessary conversations. In this case, the question that needed answering before beginning TDD was: "How does one efficiently solve a sudoku puzzle (as opposed to just manipulating one)"
It's no more burden to write step definitions than it is to write RSpec directly. I use SOLID OOP to write my tests, most of the logic lives in regular old methods, and my steps look like:
Then 'the current subscription payment date should be tomorrow' do
verify_next_payment_date(Date.today + 1)
end
Since I start with the Gherkin file, I type less than 10 keystrokes to make the step.http://www.youtube.com/watch?v=9WkmgQQhVSw http://www.youtube.com/watch?v=fxZKZsqWdFw
The effects on ego tend to weed out a lot of assholes, and between this and the mental challenges of the sport, it is one of the reasons I've come to love it. (Ryron & Rener also do a pretty good job of distinguishing between self-defense and sport jiu jitsu, at least in the videos I've seen from them.)
I've also used New Relic developer mode to identify requests that have too many queries, and optimize them. At my last job I cut the queries on the homepage by 75% this way. I also used it to look at GC, tuned that, and cut the total response time by about 80%
"'Deserve's got nothin' to do with it" (http://www.youtube.com/watch?v=dpDkYZWeeVg)
I'm "entitled" to a market rate. I'm also "entitled" to negotiate for the highest rate the market will bear.
I write a specification (using RSpec) of some behavior. It fails.
Then I write code to make that specification pass.
Now the code is working correctly, as I have defined it.
Then I refactor, using my specs (tests) as a safety net to ensure that everything after the refactor still works as intended.
This is a _VERY_ different approach than coming up with some solution in my head, implementing it (most likely with bugs), and then using tests to find and eliminate as many bugs as possible (but usually not all of them).
Any errors that make it through to a commit, when I am doing TDD, are errors in how I have specified (or failed to specify) the behavior. Any errors in the design or implementation of the solution are caught by building to a spec in very small steps.
That's the key difference between properly done BDD/TDD and other testing. Writing the tests prevents bugs instead of catching them, and it ensures that behavior does not change after refactoring.
It may be a subtle distinction, but in practice it makes a huge impact.
Red -> Green is the sampling and Green -> Refactor is the appraising.
It is not.
It is about enabling easy refactoring. The other stuff - preventing regressions, catching bugs, etc. is gravy.
I don't think doing TDD alone will make or break a startup.
But I will say this - when your startup requires 40 developers to maintain the "festering pile of code" instead of the 4-6 that should be required, you are wasting investor dollars.
When prospective candidates for employment see your code and run away from the interview, you are wasting time & money.
When your developers get burnt out dealing with that pile of crap, and your annual turnover exceeds 100% you are wasting time, money and experience.
All of these things I have seen happen, personally, at companies I worked for.
And skipping TDD doesn't help you go faster. The only timescale I've seen where it seems that TDD slows me down, is on the order of minutes. Even after working a couple of hours, I'm ahead of the game because my code works - I don't spend time with a debugger, and I won't have to do a week of refactoring next month just to add a new feature.
This is NOT TDD:
1 - Think of a solution
2 - Imagine a bunch of classes and functions that you just know you’ll need to implement (1)
3 - Write some tests that assert the existence of (2)
4 - Run all tests and fail
5 - Implement a bunch of stuff
6 - Run all tests and fail
7 - Debug
8 - Run the tests and succeed
9 - Write a TODO saying to go back and refactor some stuff later.
see: http://cumulative-hypotheses.org/2011/08/30/tdd-as-if-you-me...