views:

241

answers:

10

Hi all,

So - management is looking to do a push to move towards doing unit-testing in all the applications moving forward - and eventually get into full TDD/Continuous Integration/Automated build mode (I hope). At this point however we are just concerned about getting everyone developing apps moving forward using unit-testing. I'd like to just start with the basics.

I won't lie - I'm by far no expert by any means in unit-testing, but I do have a good enough understanding to start the initiative with the basics, and allow us to grow togeather as a team. I'd really love to get some comments & critisism from all you experts on my plan of attack on this thing. It's a team of about 10 developers in a small shop, which makes for a great opportunity to move forward with agile development methodologies and best practices.

First off - the team consists of mainly mid level developers with a couple of junior devs and one senior, all with minimal to no exposure to unit testing. The training will be a semi-monthly meeting for about 30-60 minutes each time (probably wind up running an hour long i'd guess, and maybe have them more often). We will continue these meetings until it makes sense to stop them to allow others to catch up with their own 'homework' and experience - but the push will always be on.

Anyway - here is my lesson plan I have come up with. Well, the first two at least. Any advice from your experts out there on the actual content or structure of the lessons, etc, would be great. Comments & Critisism greatly appreciated. Thanks very much.

I apologize if this is 'too much' to post in here or read through. I think this would be a great thread for SO users looking to get into unit testing in the first place as well. Perhaps you could just skip to the 'lesson plans' section - thanks again everyone.

CLIFF NOTES - I realize this post is incredibly long and ugly, so here is the cliff notes - Lesson 1 will be 'hello world unit tests' - Lesson 2 will be opening the solution to my most recent application, and showing how to apply each 'hello world' example in real life... Thanks so much everyone for the feedback you've given me so far.. just wantd to highlight the fact that lesson 2 is going to have real life production unit tests in it, since many suggested I do that when it was my plan from the begining =)

Unit Testing Lesson Plan

Overview

Why unit test? Seems like a bunch of extra work - so why do it?

• Become the master of your own destiny. Most of our users do not do true UATs, and unfortunately tend to do their testing once in production. With unit-tests, we greatly decrease risk associated with this, especially when we create enough test data and take into account as many top level inputs as we possibly can. While not being a ‘silver bullet’ that prevents all bugs – it is your first line of defense – a huge front line, comparable to that of the SB championship Giants.

• Unit-Testing enforces good design and architecture practices. It is ‘the violent psychopath who maintains your code and knows where you live’ so to say. You simply can’t write poor quality code that is well unit-tested

• How many times have you not refactored smelly code because you were too scared of breaking something? Automated testing remove this fear, makes refactoring much easier, in turn making code more readable and easier to maintain.

• Bottom line – maintenance becomes much easier and cheaper. The time spent writing unit tests might be costly now – but the time it saves you down the road has been proven time and time again to be far more valuable. This is the #1 reason to automate testing your code. It gives us confidence that allows us to take on more ambitious changes to systems that we might have otherwise had to reduce requirements on, or maybe even not take on at all.

Terminology Review

• Unit testing - testing the lowest level, single unit of work. E.G. – test all possible code paths that a single function can flow through.

• Integration testing - testing how your units work together. E.g. – run a ‘job’ (or series of function calls) that does a bunch of work, with known inputs - and then query the database at the end and assert the values are what you expect from those known inputs (instead of having to eye-ball a grid on a web page somewhere, e.g. doing a functional test).

• Fakes – a fake is a type of object whose purpose is to use for your testing. It allows you too easily not test code that you do not want to test. Instead of having to call code that you do not want – like a database call – you use a fake object to ‘fake’ that DB call and perhaps read the data from an XML/Excel file or a mocking framework. o Mock – a type of fake to which you make assert statements against. o Stub – a type of fake to which you use as placeholder code, so you can skip the database call, but do not make asserts against

Lessons

Lesson one – Hello Worlds

• Hello World unit test - I will create a ‘hello world’ console application that is unit tested. Will create this application on the fly during the meeting, showing the tools in Visual Studio 2008 (test-project, test tools toolbar, etc.) that we are going to use along the way while explaining what they do. This will have only a single unit-test. (OK, maybe I won’t create it ‘on the fly’ =), have to think about it more). Will also explain the Assert class and its purpose and the general flow of a unit-test.

• Hello World, a bit more complicated. Now our function has different paths/logical branches the code can flow through. We will have ~3 unit tests for this function. This will be a pre-written solution I make before the meeting.

• Hello World, dependency injection. (Not using any DI frameworks). A pre-written solution that builds off the previous one, this time using dependency injection. Will explain what DI is and show a sample of how it works.

• Hello World, Mock Objects. A pre-written solution that builds off the previous one, this time using our newly added DI code to inject a mock object into our class to show how mocking works. Will use NMock2.0 as this is the only one I have exposure to. Very simple example to just display the use of mock objects. (Perhaps put this one in a separate lesson?).

• Hello World, (non-automated) Integration Test. Building off the previous solution, we create an integration test so show how to test 2 functions together, or entire class together (perhaps put this one in a separate lesson?)

Lesson two – now we know the basics – how to apply this in real life?

• General overview of best practices o Rule #1- Single Responsibility Principal. Single Responsibility Principal. Single Responsibility Principal. Facetiously stating that this is the single most important thing to keep in mind while writing your code. A class should have only one purpose; a function should do only one thing. The key word here is ‘unit’ test – and the SRP will keep your classes & functions encapsulated into units. o Dependency Injection is your second best friend. DI allows you to ‘plug’ behavior into classes you have, at run time. Among other things, this is how we use mocking frameworks to make our bigger classes more easily testable.

o Always think ‘how will I test this’ as you are writing code. If it seems ‘too hard to test’, it is likely that your code is too complicated. Re-factor until it into more logical units of classes/functions - take that one class that does 5 things and turn it into 5 classes, one which calls the other 4. Now your code will be much easier to test – and also easier to read and refactor as well.

o Test behavior, not implementation. In nutshell, this means that we can for the most part test only the Public functions on our classes. We don’t care about testing the private ones (implementation), because the public ones (behavior) are what our calling code uses. For example... I’m a millionaire software developer and go to the Aston Martin dealership to buy myself a brand new DB9. The sales guy tells me that it can do 0-60 in 3 seconds. How would you test this? Would you lift the engine out and perform diagnostics tests, etc..? No... You would take it onto the parkway and do 160 MPH =). This is testing behavior vs. implementation.

• Reviewing a real life unit-tested application. Here we will go over each of the above ‘hello world’ examples – but the real life versions, using my most recent project as an example. I'll open a simple unit test, a more complex one, one that uses DI, and one that uses Mocks (probably coupled to the DI one). This project is fairly small & simple so it is really a perfect fit. This will also include testing the DAL and how to setup a test database to run these tests against.

+1  A: 

I like it. One comment I have is it's probably worth dropping in some guidelines/advice on how to go about testing threaded/asynchronous code.

Also, I'd make a comment about boundary testing i.e. using unit tests to state your assumptions on any 3rd party API you utilize.

Strawberry
thanks! I'm glad you liked it... will look more into the boundary testing. Thanks again
dferraro
+2  A: 

Is a classroom style approach going to work well? That's a question that comes to my mind as what may happen here is so much of a separation between knowledge and application of the ideas that while they may know it, they don't quite get how to use it.

Just to give a different approach, one could jump into whatever projects are going on and start trying to apply some of this which may take a while to trickle down through everyone as the idea would be that you'd train one and then as a pair you'd train another pair and so on, to try to get everyone up to the same point. This way the theoretical stuff that isn't relevant gets taken out early.

Another idea would be to see if it makes sense to try to form teams within the group of 10, such as 2 groups of 3 and a group of 4 or 5 pairs, so that each person can present what they've done and how well do they use this.

I'd second the boundary testing, as well as showing why it can be useful to test with invalid input to see that this doesn't cause the system to roll over and die.

Lastly, it may make sense to have time devoted to handling questions and other 1:1 issues that people have as not everyone will want to ask questions in front of a large group.

EDIT: A caution on Lesson 2, which is a good idea but has a few hazards to note. First, be humble in doing this lesson so that you aren't coming across as a show-off wanting to slam everyone else. Second, I'd suggest leaving some tests incomplete when going over things so that there is an interactive aspect here that prevents the other developers from merely tuning out or rolling their eyes. Third, a follow-up lesson or two of others showing what they did or you going over what they did may be more useful to show that this is a team effort in some ways.

JB King
thanks for the response. Good idea with the 1:1 questions and such.But again, Lesson 2!!! I realize my post was incredibly long and hard to read, but Lesson 2 is about taking my most recent production app and showing how to apply this unit testing 'stuff' in real life... So Lesson 2 we will open my most recent app and go over how I applied the SRP and DI to make my code testable
dferraro
"How to Win Friends and Influence People" has some good ideas that apply here like giving the other person a fine reputation and smile that may help.
JB King
dferraro
+2  A: 

I think it will be harder to start with test-after type of testing. A major difference compared to test-first is that test-after does not guide you in writing code which is easy to test. Unless you have first done TDD, it will be very hard to write code for which it's easy to write tests.

I recommend starting with TDD/BDD and after getting the hang of that, continue learning how to do test-after on legacy code (also PDF). IMO, doing test-after well is much harder than test-first.

In order to learn TDD, I recommend finishing this tutorial. That should get you started on what kind of tests to write.

Esko Luontola
I see your point.. but I disagree.. I think starting with 'test after' is probably easier to swallow... also keep in mind as I said I am no 'expert', but know enough to get the team started... even myself I am just starting to progress into doing true TDD...test after is more 'familar' to everyone as well.. TDD might seem too hard at first, which is why i'm just trying to start with 'unit testing', and then progress into TDD eventually
dferraro
+3  A: 

Getting people interested in unit testing on someone else's/sample code will not be as effective as having folks "start with the testcase" on some new code they have to write. Cover the basics of starting with a test case and triangulating to a valid result (all the while emphasizing that a failing test is progress).

That said, my personal experience is that things like this are better done in a pair programming environment. It's extra work for you, but the combination of one-on-one, some ownership and a working example they can reference at the end of it will make adoption much easier.

Getting a coverage tool running at some point later on can, given the right environment, provide a fun, competitive and gratifying way of marking progress. Making this in any way part of compensation/bonuses is a real bad idea. Done correctly, folks will adopt it because it works.

zznate
Agreed on all counts.
Dave Sims
thanks... this is a great idea. It will be easier to swallow for someone when it's their own code..BUT - won't this be very difficult because all their code has never taken into account unit testing at all? Imagine having to start writing unit tests on a 6 month long project, at almost the end of the projects life...
dferraro
... It could prove to be very difficult if not impossible due to time constraints forcing you to not be able to refactor.. which is why i wanted to start with basic good design principals so that moving forward they could use it..
dferraro
I would suggest picking a small, atomic unit of functionality for each person and go from there - the easier the better. It's the integration into the day-to-day routine that is the hardest part. Something small would make it more digestible to them and quicker for you to execute. After everyone has had an overview, you can do something like pick one or two of the most heinous, slow portions of code for a "brown bag" and refactor those with everyone in the room. Definitely keep it simple now so you have something to build off of for the next project.
zznate
+6  A: 

I like the commitment and thoroughness you're expressing here, but my feeling is your adoption plan is going to take longer this way than if you started straight in, paired up and wrote some actual production tests. You'll need infrastructure anyway -- why not bootstrap your actual production code with a CI server and a TDD framework? (A lot of times this is a bigger pain point than learning "asserts." Get this part out of the way sooner rather than later). Then sit down and solve an actual problem with a fellow coder. Pick a simple bug or small feature and try to identify what the failing tests would look like and try to write them.

I like Hello Worlds for a brown bag lunch or something. But I can't think of any reason not to just jump right in and solve some problems.

Dave Sims
+1 for starting with the infrastructure.
Esko Luontola
thanks for the insight. Lesson 2 is basically just what you said - we jump right into my most recent production app that's fully unit tested, and look at how we did these unit tests.I think without the 'hello world' foundation, it would be a bit much to swallow. Probably not for the more senior guys, but probably for the junior devs who don't even know what a unit test is.. I don't want to spoon feed them too long, so hopefully we can zoom through those 'hello world' examples quickly... still think it's overkill?? thanks again
dferraro
I helped roll out TDD a year ago to a team of mixed junior and senior devs. Basically I did a bit of pairing with each member, starting with simple tests. Once they saw a test actually written, technically it really wasn't a problem. Simple classical asserts are pretty easy to understand and implement if you already have your infrastructure in place. From there it was mostly a matter of getting the team to integrate TDD naturally into their daily workflow, where it felt natural. I had a copy of JUnit Recipies laying around, and a few links. Pretty soon they owned it for themselves.
Dave Sims
It sounds like you have a pretty good handle on TDD yourself. I think your approach is sound, but I would say the real work is continuing to mentor the coders and help them past whatever mental blocks keep TDD from becoming second-nature. This requires more of an individualized approach -- some will take to it right off like a fish to water, other will continue to want to "just code it." For those that have a harder time integrating TDD into their daily workflow, nothing can replace pairing and mentoring by someone who already knows how to do it.
Dave Sims
Also, bonus points for emphasizing SRP. One of the biggest benefits of TDD is encouraging this principle.
Dave Sims
+1  A: 

Just because I have been doing something where TDD (and unit tests in general) has been invaluable and because it might make a good demo, I'd suggest an example using Regex, especially if you aren't a Regex expert, it's quite easy to make mistakes.

It'd also make for a good 'show, not tell' demo. If you just:

step 1: Tell them just to write some code with a Regex to match a certain pattern.

step 2: expand the pattern to something else.

step 3: show how unit tests immediately let them be sure that a) it matches the new pattern and b) it hasn't broken any of the previous matches or let through results that shouldn't be.

In general C&C of your review, it's hard to get an idea of exactly what sequence events will go in. Also, semi-month seems far too infrequently if there's just a void inbetween with no push to apply their knowledge. The last thing you want is it to become some meeting they go to and forget about as soon as they are out of the room!

Do you have sufficient downtime in your current workload to address technical debt and get the team to actually pair up and start looking at adding unit tests to some existing functionality and refactoring where necessary to allow mocking?

I would say that will prove far more effective than lectures and potted workshops, YMMV.

Wysawyg
thanks for the feedback. Do we have sufficient downtime?? Unfortunately, not right now... but hopefully once new year starts we will... Hence, why I am spending my whole Sunday trying to revise this lesson plan and create the hello world solutions =).. Tommorow will be the first lesson and I fear that we aren't taking it seriously enough / devoting enough time for it. And you're right, my biggest fear is the team leaving the meeting ready to forget what theyve learned =P
dferraro
+3  A: 

I helped design and run a Test Driven Development training in house at my company for Java developers. We ended up doing it as a full day training, and we broke it up similar to how you have here.

  1. Why do testing?
  2. Simple example.
  3. More complex example.

One thing I must stress though, is people need to learn by doing.

For my training, we set up a lab environment where each student could start with the same snapshot of code, and develop and run the tests themselves, with instructors walking around the training room helping individuals who were confused or weren't getting it.

For the "Simple Example" we had a "cooking show" version of code that was on a projector and went through the TDD process step by step. Developers would have to go through the process of writing a test, then creating implementation that was just sufficient to pass the test, then repeat. At each phase, a prepared solution to the current phase was shown on the projector after the students had some time to try it on their own.

For the "Complex Example" we created a set of requirements, and then allowed the students to come up with their own solutions using TDD to do so. We even had an exercise where requirements surprise, surprise changed suddenly part way through the exercise.

I like your idea of doing it over a longer period of time with regular checkups. One downfall of our training was a lack of followup. Developers benefited by the training, I'm sure, but many I think, did not take the practice back to their ordinary work. Regular checkups would help instill unit testing as a matter of habit.

For more ideas, check out my answer to this question.

Justin Standard
thanks Justin. I don't know if / when we will ever have a whole day to stop and have a full training session... But I liked your ideas... especially changing the requirements mid-training. Awesome =)
dferraro
+1  A: 

I think it's a good outline for a session or two. But I'd strongly recommend standing up a testing infrastructure first and setting the developers up for early success, rather than shoving the developers head-first into the unit testing world and shouting "ready, set, GO!"

Assuming you develop in an IDE, a one-click test generator tool will really help with step one, test creation. You'll want to integrate a GUI-based test runner with your IDE, and you really need an automated test runner integrated with your CI builds. Having a code coverage tool integrated with the test runner tool will help developers increase their coverage. You need to provide developers with a complete framework for writing the tests, and that includes documenting how to organize the test source code (test naming standards, project settings, folder locations, etc.) Tools that automate any of the manual steps will go a long way. Provide a mock framework including mock objects for any in-house or third-party library APIs you depend on. If you're going to recommend a database full of fake data (instead of a suite of fake data access objects) create one with a few key tables and populate it with test data, and provide whatever it takes to reset it between testing runs. If not, provide a few fake DAOs of your own to serve as examples. You should have a small suite of tests already in your source control system testing production code to show as examples. And you need to have all this documented so you can hand it out in meeting #1. (Don't forget to test the document!)

One of our teams tried the ad hoc approach a couple years ago, but adoption was poor. Without the definitions or the organization of the tests, individual developers kind of did their own thing. Lack of a naming standard made it difficult to identify the right tests to run for which modules, and few people bothered. We had no IDE integration for any of it. Nobody wanted to write mocks, and we didn't provide mocks in advance for the framework we use. And it's really hard to add tests to legacy code that wasn't written with Dependency Injection in mind.

I'm now trying to get our infrastructure reorganized and ready for another shot at the task. Only after it's standing up will I work on trying to get the developers to use it again.

However, you have the one thing we were kind of lacking last time, and that's strong management support. Our previous managers were kind of lukewarm to the idea because they didn't want to delay coding by having to write all these tests. We now have new management and they are focusing on code quality -- automated unit tests are now in vogue, so it's a good time for us to try again.

John Deters
Thanks much for the advice.. It's great to get advice from people that have done this before and have learned from there mistakes. So far we have no real standards set - I didn't plan on introducing that till later as I feared it would overwhealm the group (learning standards for stuff they dont use yet?). But probably you are right - better to start on the right foot. I may wait till lesson 2 or 3 - so we dont overwhealm the group, and also to buy time to set standards (as I haven't quite decided on which TDD framework or mocking API to use as a standard yet). So far I have been using MSTEST
dferraro
.. which has turned out to be buggy and unreliable, and breaks builds randomly with error messages that don't make sense due to referencing and code-gen related issues.. I've also been using NMock2.0 - which has worked great so far, but I haven't done any huge projects with it yet. I heard that Typemock is the best - it even does Sharepoint mocks which no other API can do at the moment AFAIK (we use sharepoint heavily here at work). Its also not open source, which means if something doesnt wrk we have some1 we can call to help us fix it.. (n no, I'm not anti OSS; try 2 use it as much as i can)
dferraro
+1  A: 

I tried to introduce TDD in a small team (7-9 programmers). Lectures failed. People are skeptical, TDD sounds like snake oil. Plus, there's always the proverbial Willy.

In the end, instead of TDD, people on the team do occasional DDT (Development-Driven Tests): the write unit tests after the code to confirm that it does what they meant it to. Looks like utter failure until you realize that even this form of developer-driven QA is much better than what you had before because, even if it doesn't really improve the code for the future revisits, it cuts into deployed bugs.

There was, however, one key difference compared to your setting: the push didn't come from the management, they were just good enough to let the programmers decide for themselves.

The lectures failed, but I didn't put all the eggs in a single basket. I wrote a bunch of tests for many of the smaller, more focused functions and classes used across the project. Those little buggers you occasionally need to bend a bit to fit exactly in the next call site, hoping you didn't break any of the existing ones (you looked around the code, the change seemed safe). It was running the tests after every other svn up (not only by me), and the subsequent replies to the guilty commit emails "you broke it, pls fix ASAP!" that finally convinced them of at least partial utility of unit tests.

No matter what examples you come up with for the lectures, they'll tell you it only worked because the CUT was unrealistically (or unusually) isolated from the rest of the code. They'll ask you to write unit tests for a convoluted, buggy God Class (preferably a Singleton, everybody loves global variables, no?) because, to the best of their knowledge, that's what real-world code looks like, and they think it'll look like that with TDD as well.

Screw lectures. Have the management buy everybody a few books for the Christmas: at the very least TDD By Example (Beck) and Refactoring (Fowler). Speaking from experience, the books won't necessarily have any impact with most of them (more of the "that wasn't real-world enough" bullshit), but it's bound to stick to someone, and, no offense intended, I'd bet $1000 that Beck is the better evangelist of you two. ;) Practice TDD yourself in code you share with them. If it really works, they'll follow. Sss-looowww-lyyyy, but they will.

Then again, maybe you don't have that many Willies on your team, maybe your colleagues are eager, just in need of a leader, I don't really know, you didn't tell.

First commandment: don't be put off by slow and/or partial intake: every inch counts! (I'm preaching to myself here, ya know...)

just somebody
thanks a lot for the advice. I'm def. worried that the team won't take to it or think it's snake oil - but I have one huge advantage - management very much wants unit testing to become a best practice that we will use in all projects going forward. I just happen to be the 'lucky' guy to initiate this push =)
dferraro
+1  A: 

If management is serious about this, then they have to take the bull by the horns and take action themselves not just have a little training. Start having them ask to see the tests in code reviews. Once this is common and people know they willnot pass the code reveiw without a test, then have them refuse to deploy new code until there are tests written. It will only take a couple of iterations of this before people will know that they can't get away wihtout writing the tests and then they will do so fairly reliably. Yes the first time the manager refuses to deploy, something will probably be late. They have to take that into accound in their own planning. They also will need to start adding test writing time to their task estimating. No one wants to do this if they think they will be blamed because it takes longer to write tests and code than just to write code. Have a published schedule for how this will be implemented (you need to give them some time to learn how to do this before it is required for all new development) and on the drop dead date, nothing gets to prod without tests.

HLGEM