|
Red Squirrel Reflections
Dave Hoover explores the psychology of software development
|
|
Wed, 26 Jan 2005Quote Manager Project -- Day 8 An ongoing pet project blog...
I could pass the test by changing the hard-coded values of the
My first instinct is to extract interfaces from
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 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.
|