Red Squirrel Reflections
Dave Hoover explores the psychology of software development

Dave Hoover
dave.hoover@gmail.com

Categories
All [Atom]
Craftsmanship [Atom]
Dynamic [Atom]
Intersection [Atom]
Learning [Atom]
Links [Atom]
Polyglot [Atom]
Projects [Atom]
XP [Atom]
Old Blog

Obtivian Blogs

Andy Maleh
Colin Harris
Fred Polgardy
Jim Breen
Kevin Taylor
Todd Webb
Turner King
Tyler Jennings

Archives

March 2009 (1)
January 2009 (1)
December 2008 (1)
October 2008 (3)
September 2008 (1)
June 2008 (4)
April 2008 (3)
March 2008 (1)
February 2008 (1)
August 2007 (1)
July 2007 (1)
June 2007 (1)
May 2007 (4)
April 2007 (3)
March 2007 (5)
February 2007 (6)
January 2007 (6)
December 2006 (10)
November 2006 (5)
October 2006 (8)
September 2006 (8)
August 2006 (5)
July 2006 (12)
June 2006 (7)
May 2006 (5)
April 2006 (5)
March 2006 (4)
February 2006 (2)
January 2006 (5)
December 2005 (5)
November 2005 (3)
October 2005 (3)
September 2005 (6)
August 2005 (4)
July 2005 (7)
June 2005 (14)
May 2005 (6)
April 2005 (8)
March 2005 (9)
February 2005 (11)
January 2005 (16)
Old Archives

 

Wed, 26 Jan 2005

Quote Manager Project -- Day 8

An ongoing pet project blog...

I could pass the test by changing the hard-coded values of the Quote I'm passing into the Manager (and provide an equals method), but I'd rather get to the point and inject the dependencies that AddButtonActionListener needs to do its work.

My first instinct is to extract interfaces from PersonPanel and SourcePanel in order to mock them more easily, but they would just be a bunch of getters. Bletch. But since I can't think of anything better right now, that's just what I'll do.

Ahh, back to green. That was painful, and I'm doubting if all of the extra steps I just took were worth it. I had IDEA generate equals (and hashCode) for all of my model classes so that I could verify that the expected Quote matched the Quote created by AddButtonActionListener. I also had to create two interfaces: PersonComponent and SourceComponent.

Here's the test:

    public void testShouldProvideNewQuoteToManager() {
        Collection<Person> authors = new HashSet<Person>();
        String firstName = "Dee";
        String lastName = "Hock";
        String url = "http://chaordic.org/";
        authors.add(new Person(firstName, lastName, url));

        String text = "Community is not about profit, it is about benefit.  We confuse them at our peril.";
        String title = "Birth of the Chaordic Age";
        String page = "43";
        String isbn = "1576750744";
        Source source = new Source(title, page, isbn);
        Quote expected = new Quote(text, source, authors, null);

        Mock manager = mock(Manager.class);
        manager.expects(once()).method("newQuote").with(eq(expected));

        Mock mockSource = mock(SourceComponent.class);
        mockSource.expects(once()).method("getTitle").will(returnValue(title));
        mockSource.expects(once()).method("getPage").will(returnValue(page));
        mockSource.expects(once()).method("getIsbn").will(returnValue(isbn));

        Mock mockAuthor = mock(PersonComponent.class);
        mockAuthor.expects(once()).method("hasText").will(returnValue(true));
        mockAuthor.expects(once()).method("getFirstName").will(returnValue(firstName));
        mockAuthor.expects(once()).method("getLastName").will(returnValue(lastName));
        mockAuthor.expects(once()).method("getUrl").will(returnValue(url));

        Collection<PersonComponent> mockAuthors = new HashSet<PersonComponent>();
        mockAuthors.add((PersonComponent) mockAuthor.proxy());

        AddButtonActionListener actionListener = new AddButtonActionListener((Manager) manager.proxy(),
                                                                             text,
                                                                             (SourceComponent) mockSource.proxy(),
                                                                             mockAuthors);
        actionListener.actionPerformed(null);
    }
Ack! That's a bunch of crap. I don't like it. And here's the class...
package com.redsquirrel.com.quotes.gui;

import com.redsquirrel.quotes.model.Person;
import com.redsquirrel.quotes.model.Quote;
import com.redsquirrel.quotes.model.Source;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collection;
import java.util.HashSet;

public class AddButtonActionListener implements ActionListener {
    private final Manager manager;
    private final String text;
    private final SourceComponent source;
    private final Collection<PersonComponent> authors;

    public AddButtonActionListener(Manager manager, String text, SourceComponent source, Collection<PersonComponent> authors) {
        this.manager = manager;
        this.text = text;
        this.source = source;
        this.authors = authors;
    }

    public void actionPerformed(ActionEvent e) {
        Source source = new Source(this.source.getTitle(), this.source.getPage(), this.source.getIsbn());

        Collection<Person> authors = new HashSet<Person>();
        for (PersonComponent author : this.authors)
            if (author.hasText())
                authors.add(new Person(author.getFirstName(), author.getLastName(), author.getUrl()));

        //Person quoter = new Person(quoterPanel.getFirstName(), quoterPanel.getLastName(), quoterPanel.getUrl());
        Quote quote = new Quote(text, source, authors, null);

        manager.newQuote(quote);
    }
}
The quote I'm testing doesn't have a quoter, so I'm not handling that case yet. I guess that would be a good next step...

Here's a new test that includes a quoter...

    public void testShouldProvideNewQuoteWithOptionalQuoterToManager() {
        Collection<Person> authors = new HashSet<Person>();
        String firstName = "Frederick";
        String lastName = "Brooks";
        String url = "http://www.cs.unc.edu/~brooks/";
        authors.add(new Person(firstName, lastName, url));

        String quoterFirst = "Jim";
        String quoterLast = "McCarthy";
        String quoterUrl = "http://www.mccarthy-tech.com/";
        Person quoter = new Person(quoterFirst, quoterLast, quoterUrl);

        String text = "I can't emphasize enough the importance of empowerment, of the team being accountable to itself for its success.";
        String title = "The Mythical Man Month";
        String page = "279";
        String isbn = "0201835959";
        Source source = new Source(title, page, isbn);
        Quote expected = new Quote(text, source, authors, quoter);

        Mock manager = mock(Manager.class);
        manager.expects(once()).method("newQuote").with(eq(expected));

        Mock mockSource = mock(SourceComponent.class);
        mockSource.expects(once()).method("getTitle").will(returnValue(title));
        mockSource.expects(once()).method("getPage").will(returnValue(page));
        mockSource.expects(once()).method("getIsbn").will(returnValue(isbn));

        Mock mockAuthor = mock(PersonComponent.class);
        mockAuthor.expects(once()).method("hasText").will(returnValue(true));
        mockAuthor.expects(once()).method("getFirstName").will(returnValue(firstName));
        mockAuthor.expects(once()).method("getLastName").will(returnValue(lastName));
        mockAuthor.expects(once()).method("getUrl").will(returnValue(url));

        Collection<PersonComponent> mockAuthors = new HashSet<PersonComponent>();
        mockAuthors.add((PersonComponent) mockAuthor.proxy());

        Mock mockQuoter = mock(PersonComponent.class);
        mockQuoter.expects(once()).method("getFirstName").will(returnValue(quoterFirst));
        mockQuoter.expects(once()).method("getLastName").will(returnValue(quoterLast));
        mockQuoter.expects(once()).method("getUrl").will(returnValue(quoterUrl));

        AddButtonActionListener actionListener = new AddButtonActionListener((Manager) manager.proxy(),
                                                                             text,
                                                                             (SourceComponent) mockSource.proxy(),
                                                                             mockAuthors,
                                                                             (PersonComponent) mockQuoter.proxy());
        actionListener.actionPerformed(null);
    }
That fails (after I add a new constructor to take a PersonComponent). Looks like it should be easy to get back to green... Yep, it was.
    public void actionPerformed(ActionEvent e) {
        Source source = new Source(this.source.getTitle(), this.source.getPage(), this.source.getIsbn());

        Collection<Person> authors = new HashSet<Person>();
        for (PersonComponent author : this.authors)
            if (author.hasText())
                authors.add(new Person(author.getFirstName(), author.getLastName(), author.getUrl()));

        Person quoter = null;
        if (this.quoter != null)
            quoter = new Person(this.quoter.getFirstName(), this.quoter.getLastName(), this.quoter.getUrl());

        Quote quote = new Quote(text, source, authors, quoter);

        manager.newQuote(quote);
    }
Now that I've got a green bar, I think I'd like to do some refactoring to make this code a bit easier on the eyes. Why not give the model objects additional constructors to take in their corresponding component objects? I was reluctant to do this before because I didn't want to couple the model to GUI code, but since they're just simple interfaces, I'm OK with it now...
    public void actionPerformed(ActionEvent e) {
        Source source = new Source(this.source);

        Collection<Person> authors = new HashSet<Person>();
        for (PersonComponent author : this.authors)
            if (author.hasText())
                authors.add(new Person(author));

        Person quoter = null;
        if (this.quoter != null)
            quoter = new Person(this.quoter);

        Quote quote = new Quote(text, source, authors, quoter);

        manager.newQuote(quote);
    }
Ahh, that's much better. And the tests are still passing. Time to test the for-if condition when there are multiple AuthorComponents.
    public void testShouldProvideNewQuoteWithTwoAuthorsToManager() {
        Collection<Person> authors = new HashSet<Person>();
        String firstName = "Mary";
        String lastName = "Poppendieck";
        String url = "http://poppendieck.com/";
        authors.add(new Person(firstName, lastName, url));

        String firstName2 = "Tom";
        String lastName2 = "Poppendieck";
        String url2 = "http://poppendieck.com/";
        authors.add(new Person(firstName2, lastName2, url2));

        String text = "It is necessary to have a reasonable failure rate in order to generate a reasonable amount of information.";
        String title = "Lean Software Development: An Agile Toolkit";
        String page = "19";
        String isbn = "0321150783";
        Source source = new Source(title, page, isbn);
        Quote expected = new Quote(text, source, authors, null);

        Mock manager = mock(Manager.class);
        manager.expects(once()).method("newQuote").with(eq(expected));

        Mock mockSource = mock(SourceComponent.class);
        mockSource.expects(once()).method("getTitle").will(returnValue(title));
        mockSource.expects(once()).method("getPage").will(returnValue(page));
        mockSource.expects(once()).method("getIsbn").will(returnValue(isbn));

        Mock blankAuthor = mock(PersonComponent.class);
        blankAuthor.expects(once()).method("hasText").will(returnValue(false));

        Mock mary = mock(PersonComponent.class);
        mary.expects(once()).method("hasText").will(returnValue(true));
        mary.expects(once()).method("getFirstName").will(returnValue(firstName));
        mary.expects(once()).method("getLastName").will(returnValue(lastName));
        mary.expects(once()).method("getUrl").will(returnValue(url));
`
        Mock tom = mock(PersonComponent.class);
        tom.expects(once()).method("hasText").will(returnValue(true));
        tom.expects(once()).method("getFirstName").will(returnValue(firstName2));
        tom.expects(once()).method("getLastName").will(returnValue(lastName2));
        tom.expects(once()).method("getUrl").will(returnValue(url2));

        Collection<PersonComponent> mockAuthors = new HashSet<PersonComponent>();
        mockAuthors.add((PersonComponent) blankAuthor.proxy());
        mockAuthors.add((PersonComponent) mary.proxy());
        mockAuthors.add((PersonComponent) tom.proxy());

        AddButtonActionListener actionListener = new AddButtonActionListener((Manager) manager.proxy(),
                                                                             text,
                                                                             (SourceComponent) mockSource.proxy(),
                                                                             mockAuthors);
        actionListener.actionPerformed(null);
    }
And that passes without any changes to the code. Good times. I wish those test cases were shorter.

[/projects/quotes] permanent link


powered by blosxom