About 4 years ago I was consulting for one of the largest companies in Israel (about 15,000+ employees) and helping them grok TDD and automated unit testing. I had spent there over a year and a half, on and off, and was teaching both .net, C++ and java developers about the techniques and tools they can use.
I was consulting for C++, Java and .NET developers.
The C++ devs were first.
They had plenty of objections to what I was teaching them, because the amount of code refactoring they needed to do in order to make their code testable was almost unrealistic for them. Even writing new code meant writing something on top of old-untestable code, and they had a hard time adjusting to the more interface (pure virtual) based, contract driven TDD approach for new code. C++ devs has a hard time becuase it takes more code to write good testable code, and the coding tools they used (VS on windows and VI and such on UNIX based systems) didnt’ provide a good productive way to make code “bend” easily as they refactored it or created it in the first place. every little change meant a big “search and replace”. every method refactoring meant adding it the declaration in an .h file and thus they tried to have a little to do with changing code as possible.
Looking back, today they still don’t have the right infrastructure in place or the right tooling or the willingness to do unit testing or TDD becuase it’s too hard with the current tooling to accomplish it.
I had also spent some time with the .NET devs
and thought them the same things. Becuase tooling support was better it was a bit easier for them to accept the practices, but still people had a lot of trouble grasping concepts such as dependency injection, inversion of control and designing for testability in general. I found that most people didn’t really have a big problem with the idea of exposing things they didn’t use to expose before, it was more about the concept of learning how to refactor the code in interesting OO ways to make it testable. Making a class non sealed, a method virtual, and then inheriting from that class and testing against the inherited class (“extract and override”) was very much unintuitive for them, and other concepts like containers (spring at the time) didn’t help them get into the groove either. For them it was like drinking water from a fire hose.
Consulting for the java devs
Java has lots going for it in terms of unit testing, automation and TDD
- Java devs have a much longer “heritage” of tooling and unit testing than in the .NET world, and so you’d expect them to be more inclined to do these things.
- JUnit is part of any decent java idea so the barrier for entry should be lower, right?
- Refactoring was also in most decent java IDE's, so you’d expect them to grok that too. but that wasn’t the case.
- class methods in java are virtual by default so you can override what you want as long as the class is not sealed (final)== more testable by default.
- Java required a much higher OO learning curve to start with. Good OO knowledge is fundamental for testable designs.
Unfortunately, most developers I met didn’t even know how to use the refactoring tools they had available and had a hard time with the same dependecy injection principles that the C++ and .NET devs had. It was just too cumbersome to make the code testable, but eventually the infa team at the company had made the decision to take a couple of weeks and make all the infra stuff replaceable for testing purposes. they did not regret that decision.
So the java group had the most success with testable designs, but they still had a hard time doing it. in fact, only the infra team at that group took the plunge. the other (higher level) devs in the java group didn’t grok it. and don’t use it today.
Why was this adoption been such a failure? They were willing to learn, but they didn’t like what they learned. It didn’t fit their needs in the real world. Or the way they worked, or the way they knew how to do things.
Think what you will ,but 99% of the dev worlds aren’t ready for the “testability” message. and that is a fact that is hard to realize for most of us, which just can’t grasp why isn’t that any sensible human being isn’t willing to do everything much better than they are doing today.
Spending the past week running around the Microsoft Campus in Redmond had made several things clearer for me. I’ve been feeling them for a while, but this week these abstract feelings materialized into something that is more coherent that I can talk about.
Why do most developers not write unit tests, still?
Reason #1: Learning curve
One of the main reasons most developers still today don’t really take to unit testing (and TDD) is the really high learning curve that is forced upon them. That is not to say that learning about
mocks, stubs, dependency injection, IoC containers, Extract & override technique, Record\Replay, AAA and more
-- is not useful, but it is a big obstacle to get people into the habit of doing something which they know is good for them – test their code, verify their assumptions, automate, integrate, be confident, get feedback, define behavior – all those and many other benefits are being thrown away by developers all over the world because the entry fee to this world is too high, for all the wrong reasons.
It’s time to simplify. Get back to basics. Take a good hard look at the huge spiral that we are leading people into and asking whether removing some things today can lead to those things being accepted and being easier tomorrow. or maybe they weren’t needed at all.
Say Goodbye to “mock” and “stub”
“mock” and “stub” have played their part in us trying to explain to ourselves what it is we are trying to do, to start learning the minute and important differences in the patterns of our test code. They helped us define what it is we are looking to do, in terms that, for lack of anything better, are now the de-facto pattern names for something that should never have been named in the first place for the casual programmer.
That’s why in the new version of Isolator we’ve chosen to completely get rid of the words mock and stub, and get back into “fake”ing. You can fake a method or a class instance, and you can verify that something was called. You should not care whether this is a stub or a mock.
We need to clean our language: mock, stub, fake, test double: They all mean something in the context of us trying to identify patterns in our test code. But as users mature and start using the frameworks, those same words increase the entry barrier, and effectively block people out. If I’m showing a simple test that fakes some dependency that a class uses to a new dev on my team, I should not have to send them over to read fowler’s “mocks arn’t stubs” article (which is enlightening, but should not be relevant to them) because it misses the point.
Less interaction testing, more state based testing
Most crappy unit tests I’ve seen were over specified in that they asserted on internal interaction with an object way too heavily, which made the tests very fragile. Getting back to basics means making it easy to enable state based testing (assert style checks) and only enabling interaction testing where it makes sense: (when your app does one-way messaging to an external component). verifying that something was called should be the exception to the unit testing rule. The AAA style APis make that a more clear distinction.
Record-replay APIs make it too easy to make interaction based tests when you really want a state based one (getting a result vs. checking that something was called)
Use AAA style Apis
As an industry we are learning and changing all the time. The “Arrange-act-assert” pattern in the field of mock frameworks represents us trying to return to basics. Moq did that nicely by showing a way that leads there, and it is being taken gladly by Rhino and Typemock Isolator. It is taking off because we are starting to look beyond our own little echo chamber and seeing what is is that all those other 99% of devs are expecting as guidance, and in what form.
AAA is guidance because there was an outcry (a silent, vote of feet) that record-replay is not cutting it – it’s too confusing for non-experts. It’s too technical. and it does not lend itself to the way people want to work. So most people didn’t use it.
Visual Basic Language Support
Everyone screwed up on this one.most VB developers who will try to use one of the current frameworks will not succeed since they required anonymous methods or lambda expressions. Some frameworks use VB specific keywords which makes them VERY awkward to use. If we want broad acceptance of our practices we need to make sure whoever wants to can use the tooling we make. VB is huge and its amazing how we all pretend it does not exist. But I think it’s the key to getting to those people who want to be better but all they see is a high wall instead of an entrance.
Use “Isolate” and “fake” instead of “mock”
Don’t say ‘mock’ frameworks, say “isolation” frameworks. Because ‘Mock’ is an overloaded word it is confusing. Explaining to a developer that you can “isolate” something from its dependencies rather than “mock” its dependencies feels more natural.
Remove APIs instead of adding to them. Have single point of entries to your frameworks because discoverability is the key to good framework use. That’s why extension methods are problematic, they mean multiple points of entry.
Be open: Realize that other tools and techniques can act as bridges to the agile world
Design for testability is just one way to get where you want to go. it is not the only way. Sometimes it’s impossible. Discouraging the use of other ways of working (using tools like Typemock Isolator or JMockit) will only make the current echo chamber more closed and feel more un-achievable for those who need to get to the same point with a very different set of constraints than what most of the “loud” voices need to face.
By “allowing” other forms of working you enable those who want to, to get a bridge to help them instead of walking by foot the whole way. Any other way would mean that we will all fail in delivering what we believe in to those who have no idea what we’re talking about.