开发者

Eat, Sleep and Breathe Unit Testing/TDD/BDD [closed]

Closed. This question is opinion-based. It is not currently accepting answers.

Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.

Closed 9 years ago.

开发者_如何学运维 Improve this question

I do write unit tests while writing APIs and core functionalities. But I want to be the cool fanboy who eats, sleeps and breathes TDD and BDD. What's the best way to get started with TDD/BDD the right way? Any books, resources, frameworks, best practices?

My environment is Java backend with Grails frontend, integrated with several external web services and databases.


A good place to start is reading blogs. Then buy the books of the people who are blogging. Some I would highly recommend:

"Uncle Bob" Martin and the guys at Object Mentor: http://blog.objectmentor.com/

P.S. get Bobs book Clean Code:

http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882

My friend Tim Ottinger (former Object Mentor dude) http://agileinaflash.blogspot.com/ http://agileotter.blogspot.com/

The Jetbrains guys: http://www.jbrains.ca/permalink/285

I felt the need to expand on this, as everyone else seems to just want to give you their opinion of TDD and not help you on your quest to become a Jedi-Ninja. The Michael Jordan of TDD is Kent Beck. He really did write the book on it:

http://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530

he also blogs at:

http://www.threeriversinstitute.org/blog/?p=29

other "famous" supporters of TDD include:

  • Tim Bray
  • Martin Fowler
  • Ward Cunningham

All are great people to follow. You should also consider attending some conferences like Agile 2010, or Software Craftsmanship (this year they were held at the same time in Chicago)


I don't like it when people say "Practice X is never bad; if it doesn't work, you're not doing it right." Sorry, it has the same feel as any other over-zealous religious dogma. I don't buy it.

I agree with those folks who say that the best solution your time and money can afford should be the goal.

Anyone who objects to TDD should not automatically be accused of disregarding quality. ("So when did you stop beating your wife?") The fact is that software has bugs in it, and the cost of eliminating all of them has to be weighed against the benefit.

The same holds true in manufacturing. Tolerances on dimensions and finishes on surfaces are not all the same, because sometimes a close tolerance and a mirror finish aren't warranted.

Yes, I write unit tests, although not often before I write the class. I've seen the effect of tests on design. I measure and watch code coverage. If I find that my coverage isn't acceptable, I write more tests. I understand the benefit of a safety net of unit tests for refactoring. I follow those practices even when I'm working alone, because I've experienced the benefits first-hand. I get it.

But I'd look askance at any teammate that started bugging me about "eating, sleeping, and breathing unit testing and TDD."

My manager says that the only way that will get me a promotion is if I can get the team to TDD/BDD.

Ever think that maybe this makes you sound like a suck-up? Have you found that your nagging has alienated the rest of your team?

This response might lose me a few reputation points, but it had to be said.

I think a better approach would be to practice it yourself and let others see the benefit. Lead by example. It'll be far more persuasive than running your mouth.

Geez, Grails has test generation built-in. If you're working on a team that uses Grails, how much more selling is needed?


Best practice IMHO: Do what is practical and not just because it is a process. Don't forget what the goal of writing applications is, and in the business world, it isn't writing tests. Don't get me wrong, they have their place, but that shouldn't be the goal.


Find someone that has been doing TDD/BDD and pair program with them.


Metrics are, IMHO, the best way to get from here to there. Keep track of how well your code is covered, keep deltas of code complexity for every commit, use test-runners that watch your code for changes and constantly re-run the corresponding tests. Never let test lengths get above a few lines, so that all your tools work well. And I'd recommend once a month, take a day off to run your code through a mutation tester. That day should be dedicated to writing tests only. All of this stuff will bring you pain if you're not already doing good TDD. Learn from the pain, and in no time at all, you'll be doing it right.

And never lose sight of what the tests are for: To describe desired behavior. They are your executable specification. (This is also why I like Cucumber; now you can get your PHB to write your tests for you! Well, maybe not quite that good, but it's close!)


"PS: My manager says that the only way that will get me a promotion is if I can get the team to TDD/BDD."

The only realistic way to get a team to do something (without killing you in the process) is to demonstrate to them clearly that it will benefit them to change their habits. In other words, write code. Lots of code. Tons of code. And then when the crucial email arrive that alters the specification radically, show them that you can change your code easily with refactoring and whats worse because you were prepared for it with your tests in place. The bar was green, hack hack hack, RED BAR!!!!, hack hack hack, green bar, go home.

Read Kent Becks book about test driven design. Start with tests and then do the code. Get a build server running which RUNS THE TESTS! You do not need ot have it for the whole team - do it for yourself and SHOW them that it helps.

Preaching only annoys natives :)


I've been doing TDD for a couple of years, but lately I've started looking more in to the BDD way of driving my design and development. Resources that helped me get started on BDD was first and formost Dan North's blog (the 'founder' of BDD). Take a look at Introducing BDD. There's also an 'official' BDD Wiki over at behaviour-driven.org with some good post well worth reading.

The one thing that I found really hard when starting out with BDD (and still find a bit hard) is how to formulate those scenarios to make them suitable to BDD. Scott Bellware is a man well skilled in BDD (or Context-Spesification as he like to coin it) and his article Behavior-Driven Development in Code Magazine helped me a lot on understanding the BDD way of thinking and formulating user stories.

I would also recomend the TekPub screencast Behavior-driven Design with Specflow by Rob Conery. A great intro to BDD and to a tool (SpecFlow) very good suited for doing BDD in C#.

As for TDD resources, there's already a lot of good recommendations here. But I just want to point out a couple of books that I can really recommend;

  • Working Effectively with Legacy Code by Michael Feathers; A must-have read if you're working on legacy code (aren't we all?) and want to get it under test
  • The Art of Unit Testing: With Examples in .Net by Roy Osherove; If you're new to unit testing, this is the book to get you started
  • Test Driven Development: By Example by Kent Beck; If you're learning TDD why not get it from the source itself? A good book - easy to read, good humour and great thoughts.
  • Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin; If Kent Beck describes the hows of TDD, Uncle Bob describes the whys.


For start do unit testing, then read about how to do it right last teach your team how to TDD and get them on board - because in my experience nothing is more important then doing unit testing with your whole team.

You'll also need a proper build process - using a build server that would build your code and run your testst I recommend using TeamCity (free with limitations).

Learning how to right good unit tests is the hard part - some of it you'll learn by yourself (as long as you keep unit testing) and the rest you can learn from searching the internet.

You'll know you've reached your goal when NOT writing unit tests as part of development will look to you just wrong.


Remember, agile means that you're not completely sold out on any particular method. If you're working on something where the benefits of TDD aren't worth it (like doing trial-and-error edits on a Swing interface), then don't use TDD.


I can't see that anybody has really expressed that TDD is not about testing. TDD-ing is about expressing the expected behaviour before doing the tiny behavioural-changing modification. This greatly improves design and enables focusing in a way I have never experienced before. You get tests that protects your future refactorings and 90% coverage for free.

To learn it I would suggest (summarising what others have said and adding one of my own):

  1. visit the blogs and read the books mentioned above
  2. pair up with someone proficient in TDD
  3. practice

I practiced the Bowling kata (exercise) on my own about 20 times (about 30 minutes apiece) before I started to see the light. Started out by analysing Uncle Bob's description of it here. There are a host of katas on the codingdojo.org site including solutions and discussions. Try them!


To take a quote from Nike: JUST DO IT.

Second piece of advice - never rely on someone else's interface. Always write, on the level of each class, to the interface you wished existed - write up an adapter to the actual implementation as necessary.

Also, I find it useful to avoid return values on methods, and to think of the code in terms of message-passing rather than function calls.

YMMV.


A year ago, I had little idea how to do TDD (but really wanted to (how frustrating)) and had never heard of BDD... now I do both compulsively. I have been in a .Net development environment, not Java, but I even replaced the "F5 - Run" button with a macro to either run Cucumber (BDD) or MBUnit (TDD) depending if it is a Feature/Scenario or Specification. No debugger if at all possible. $1 in the jar if you use the debugger (JOKING (sort of)).

The process is very awesome. The framework we are additionally using is by The Oracle I've been blessed to come across, and absorbing information from, and that framework he/we use is MavenThought.

Everything starts with BDD. Our BDD is straight up cucumber ontop of iron ruby.

Feature:

Scenario: .... Given I do blah...
When I do something else... Then wonderful things happen...

Scenario: ...

And that's not unit testing itself, but it drives the feature, scenario by scenario, and in turn the unit (test) specifications.. So you start on a scenario, and with each step you need to complete in the scenario it drives your TDD.

And the TDD we have been using is kind of BDD in a way, because we look at the behaviours the SUT (System Under Test) requires and one behaviour is specified per specification (class "test" file).

Example:

Here is the Specification for one behaviour: When the System Under Test is created.

There is one more specification (C# When_blah_happens class file) for another behaviour when a property changes, but that is separated out into a separate file.

using MavenThought.Commons.Testing;
using SharpTestsEx;

namespace Price.Displacement.Module.Designer.Tests.Model.Observers
{
    /// <summary>
    /// Specification when diffuser observer is created
    /// </summary>
    [ConstructorSpecification]
    public class When_diffuser_observer_is_created
        : DiffuserObserverSpecification
    {
        /// <summary>
        /// Checks the diffuser injection
        /// </summary>
        [It]
        public void Should_return_the_injected_diffuser()
        {
            Sut.Diffuser.Should().Be.SameInstanceAs(this.ConcreteDiffuser);
        }
    }
}

This is probably the simplest behaviour for a SUT, because in this case when it is created, the Diffuser property should be the same as the injected diffuser. I had to use a Concrete Diffuser instead of a Mock because in this case the Diffuser is a Core/Domain object and has no property notification for the interface. 95% of the time we refer to all our dependencies like Dep(), instead of injecting the real thing.

Often we have more than one [It] Should_do_xyz(), and sometimes a fair bit of setup like perhaps upto 10 lines of stubbing. This is just a very simple example with no GivenThat() or AndGivenThatAfterCreated() in that specification.

For setup of each specification we generally only ever need to override a couple methods of the specification:

GivenThat() ==> this happens before the SUT is created.

CreatSut() ==> We auto mock creation of the sut with StructureMap and 90% of time never need to override this, but if you are constructor injecting a Concrete, you have to override this.

AndGivenThatAfterCreated() => this happens after the SUT is created.

WhenIRun() => unless it is a [ConstructorSpecification] we use this to run ONE line of code that is the behaviour we are specifiying for the SUT

Also, if there is common behaviour of two or more specifications of the same SUT, we move that into the base specifcation.

All I gotta do to run the Specification is highlight it's name, example "When_diffuser_observer_is_created" and press F5, because remember, for me F5 runs a Rake task either test:feature[tag] if Cucumber, or test:class[SUT]. Makes sense to me because everytime you run the debugger it's a throw away, no code is created (oh and it costs a $1 (joking)).

This is a very, very clean way of specifying behaviour with TDD and having really, really simple SUTs and simple specifications. If you try and be cowboy coder and write the SUT crappy with hard dependencies, etc, you will feel the pain of trying to do TDD and get fed up / give up OR bite the bullet and do it right.

And here's the actual SUT. We got a little fancy and use PostSharp to add property notify changed on the Diffuser, so hence the Post.Cast<>. And again, that's why I injected a Concrete rather than Mock. Anyway, as you can see the missing behaviour defined in another specification is when anything changes on the Diffuser.

using System.ComponentModel;
using MavenThought.Commons.Events;
using PostSharp;
using Price.Displacement.Core.Products;
using Price.Displacement.Domain;

namespace Price.Displacement.Desktop.Module.Designer.Model.Observers
{
    /// <summary>
    /// Implementation of current observer for the selected product
    /// </summary>
    public class DiffuserObserver : AbstractNotifyPropertyChanged, IDiffuserObserver
    {
        /// <summary>
        /// gets the diffuser
        /// </summary>
        public IDiffuser Diffuser { get; private set; }

        /// <summary>
        /// Initialize with a diffuser
        /// </summary>
        /// <param name="diffuser">The diffuser to observe</param>
        public void Initialize(IDiffuser diffuser)
        {
            this.Diffuser = diffuser;
            this.NotifyInterface().PropertyChanged += (x, e) => this.OnPropertyChanged(e.PropertyName);
        }

        /// <summary>
        /// Gets the notify interface to use
        /// </summary>
        /// <returns>The instance of notify property changed interface</returns>
        protected INotifyPropertyChanged NotifyInterface()
        {
            return Post.Cast<Diffuser, INotifyPropertyChanged>((Diffuser)Diffuser);
        }
    }
}

In conclusion, this BDD / TDD style of development rocks. It took one year but I am a total convert as a way of life. I would not have learned this on my own. I picked up everything from The Oracle http://orthocoders.com/.

Red or Blue pill, the choice is yours.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜