> you have to rewrite all your enrollment specs to respect that new interface now, regardless of if they were shitty or well-written.
No, not if you're doing it correctly.
Again, that's not a unit test, it's a functional test. Unit tests test really small blocks of code. If you're crossing class boundaries, generally, it's not really a unit test.
Those are unit tests (not beautiful ones, written to refactor, but I digress) - they mock cross-object (and even cross method, in some cases) and they really focus on individual paths through the code. If you change a method, only tests relating to that method fail.
Edit: also, "the problem isn't that my tests are bad," is a poor assumption to start with - you're assuming a priori something that we're discussing here, which is that you're complaining about problems resulting from not testing correctly. "Bad" is a loaded word in any case - I'm not sure there's a test I've seen that couldn't be improved.
And, if you decide to rename any of the methods on the classes you've mocked out here, your unit tests will continue to pass despite the fact your implementation is now full of bugs. And, if you do rename the methods or class you've mocked, you now have to update every single test for any class coupled with the changed method.
I've never understood endo-testing/mock objects in environments where the compiler cannot check your mocked interfaces. I also don't understand how people can aruge that you shouldn't be testing against the implementation and then say in the next breath you should be mocking out every single method call the internal implementation makes explicitly. You're just setting yourself to get lots and lots of green tests on code that will explode as soon as it hits production. Whenever I've done aggressive mock object based testing I soon have zero confidence in my tests because I get burned due to the fact that the mock objects eventually start asserting that the wrong behavior is right and my code explodes when integrated.
(And yes, I know the excuse here is that you should then write integration tests and functional tests too. But seriously now, how many tests are you going to end up writing for your 100 line Ruby class before you decide you're going overboard in the name of purity?)
Better to instead just write it so instead of worrying about no obvious deficiencies there are obviously no deficiencies (avoid side effects, state, extra coupling), and write some functional tests just to be safe. Yes, those ones that actually hit the database and test the interaction between multiple classes that TDD advocates loathe because they are so slow and impure. Slow they may be, but at least I know they're testing the code that's going to run on my servers. I'd rather have 10 tests break when I change one class that are easy to fix than have zero tests break when I change one class and let broken code get to production.
> And, if you decide to rename any of the methods on the classes you've mocked out here, your unit tests will continue to pass despite the fact your implementation is now full of bugs.
Yes, that's why you have other tests to cover those implementations. This is just an isolated example.
> And, if you do rename the methods or class you've mocked, you now have to update every single test for any class coupled with the changed method.
Yes, this is true. It's a helpful thing in my experience: you wish to mock as little as possible, and so having clear end points is important. Having to change every single usage of the method means you tend to write better code, in essence.
> I've never understood endo-testing/mock objects in environments where the compiler cannot check your mocked interfaces.
That's fine, don't do it. This is just a demonstration of what works for me.
> I also don't understand how people can aruge that you shouldn't be testing against the implementation and then say in the next breath you should be mocking out every single method call the internal implementation makes explicitly. You're just setting yourself to get lots and lots of green tests on code that will explode as soon as it hits production.
This is true, however, it means that when you edit one method, at most a half dozen tests will fail, as opposed to your entire test suite failing. You end up with very good locality of failure, as opposed to binary 'something is wrong' tests.
> Whenever I've done aggressive mock object based testing I soon have zero confidence in my tests because I get burned due to the fact that the mock objects eventually start asserting that the wrong behavior is right and my code explodes when integrated.
This is true, but not something that can be avoided - you end up with problems either way, and these test (as above) give you very good feedback about where your error is. That, combined with very comprehensive testing, leads to a situation where you can trust your tests really well never to throw false positives.
> (And yes, I know the excuse here is that you should then write integration tests and functional tests too. But seriously now, how many tests are you going to end up writing for your 100 line Ruby class before you decide you're going overboard in the name of purity?)
It depends on how important it is to you - for example, the tests above test functionality that is core to a piece of code that runs on many hundreds of applications - not something you ever want to break. As a result, the investment was worth it. You have to decide those tradeoffs on your own.
> Better to instead just write it so instead of worrying about no obvious deficiencies there are obviously no deficiencies (avoid side effects, state, extra coupling), and write some functional tests just to be safe.
I'm worried about more than obvious deficiencies - I'm worried about corner cases and things you haven't thought of. In writing these tests I caught dozens of unspecified and poor behavior corner cases.
> Yes, those ones that actually hit the database and test the interaction between multiple classes that TDD advocates loathe because they are so slow and impure. Slow they may be, but at least I know they're testing the code that's going to run on my servers. I'd rather have 10 tests break when I change one class that are easy to fix than have zero tests break when I change one class and let broken code get to production.
I totally agree with that. There are comprehensive integration style tests and comprehensive functional tests too - but they're pointless without the assistance of specific tests that indicate which portion of the application is failing.
If a functional test fails without a unit test failing, you have work to do on your unit test suite. Unit testing is a tool for programming as much as it is a tool to verify correctness.
I totally agree - they're not designed to be readable, unfortunately, they're just designed to test things so that I could make sure they passed after a refactoring. As I said, there are many things I could wish to improve about them - they were just a demonstration of the point at hand (isolation)
No, not if you're doing it correctly.
Again, that's not a unit test, it's a functional test. Unit tests test really small blocks of code. If you're crossing class boundaries, generally, it's not really a unit test.
Let me show you what I mean with tests I wrote:
https://github.com/newrelic/rpm/blob/master/test/new_relic/a...
Those are unit tests (not beautiful ones, written to refactor, but I digress) - they mock cross-object (and even cross method, in some cases) and they really focus on individual paths through the code. If you change a method, only tests relating to that method fail.
Edit: also, "the problem isn't that my tests are bad," is a poor assumption to start with - you're assuming a priori something that we're discussing here, which is that you're complaining about problems resulting from not testing correctly. "Bad" is a loaded word in any case - I'm not sure there's a test I've seen that couldn't be improved.