Monday, November 2, 2009

EDIT THE RATING









































Prev don't be afraid of buying books Next






























EDIT THE RATING






Allow the rating to be
edited.





For this task, we'll need the following
tests:






Test 31.
Selecting a movie updates the rating in the GUI.










Test 32.
Updating a movie changes its rating if a different rating was
selected for it.










Test 33.
Selecting a movie from the list updates the displayed rating.










Test 34.
Updating a movie in the GUI changes its rating if a different
rating was selected for it, and updates the display
accordingly.











We'll start by adding some ratings to
TestMovieListEditor's fixture:




starWars = new Movie("Star Wars",5);
starTrek = new Movie("Star Trek",3);
stargate = new Movie("Stargate");








Test 31: Selecting a movie updates the
rating in the GUI



Next we'll extend testSelecting() to
check that a selection in the movie list causes an update of the
rating:




public void testSelecting() {
mockView.setMovies(movies);
control.setVoidCallable(1);

mockView.setNewName("Star Wars");
control.setVoidCallable(1);
mockView.setNewRating(6);
control.setVoidCallable(1);

mockView.setNewName("Star Trek");
control.setVoidCallable(1);
mockView.setNewRating(4);
control.setVoidCallable(1);

mockView.setNewName("Stargate");
control.setVoidCallable(1);
mockView.setNewRating(0);
control.setVoidCallable(1);

control.activate();

MovieListEditor editor = new MovieListEditor(movieList,mockView);
editor.select(0);
editor.select(1);
editor.select(2);

control.verify();
}







The first step to getting to a green bar
again is to add a method to the MovieListEditorView
interface:




public interface MovieListEditorView {
void setMovies(Vector movies);
String getNewName();
void setEditor(MovieListEditor anEditor);
void setNewName(String string);
void duplicateException(String string);
void setNewRating(int i);
}






Next, we need to add to
MovieListEditor.select() to set the rating as well as the
name:




public void select(int i) {
if (i == -1) {
selectedMovie = null;
} else {
selectedMovie = movies.getMovie(i);
view.setNewName(selectedMovie.getName());

try {
view.setNewRating(selectedMovie.getRating() + 1);
} catch (UnratedException e) {
view.setNewRating(0);
}
}
}






Now we see that several tests are failing due to
the additional (and unexpected) calls to setNewRating().
We can either add expectations to these tests or have them ignore
those calls. We'll do the latter since it's the simplest thing that
will work. We just need to add the following line to the beginning
of each failing test:




mockView.setNewRating(0);
control.setDefaultVoidCallable();






Upon looking over the code, I feel that some
renaming is in order. The method for getting and setting the name
and rating fields are misleading. Let's change them as so:
getNewName() changes to getNameField().For
brevity, we'll just show the interface:




public interface MovieListEditorView {
void setMovies(Vector movies);
String getNameField();
void setEditor(MovieListEditor anEditor);
void setNameField(String string);
void duplicateException(String string);
void setRatingField(int i);
}
























Test 32: Updating a movie changes its
rating if a different rating was selected for it



Now for the flip side of editing the
rating...updating the movie behind it. We'll start by extending
testUpdating() by adding ratings to the Movies we
create and adding expectations for calls to
setRatingField() and getRatingField():




public void testUpdating() {
Vector newMovies = new Vector();
newMovies.add(starWars);
newMovies.add(new Movie("Star Trek I",5));
newMovies.add(stargate);

mockView.setMovies(movies);
control.setVoidCallable(1);

mockView.setNameField("Star Trek");
control.setVoidCallable(1);
mockView.setRatingField(4);
control.setVoidCallable();

mockView.getNameField();
control.setReturnValue("Star Trek I",1);
mockView.getRatingField();
control.setReturnValue(6, 1);

mockView.setMovies(newMovies);
control.setVoidCallable(1);

control.activate();

MovieListEditor editor = new MovieListEditor(movieList,mockView);
editor.select(1);
editor.update();

control.verify();
}






To get to green, we have to do some work on
MovieListEditor.update(). We'll add a line that sets the
rating of the selected Movie if it can be renamed
successfully:




public void update() {
if (selectedMovie != null) {
String newName = view.getNameField();

try {
movies.rename(selectedMovie, newName);
selectedMovie.setRating(view.getRatingField());
updateMovieList();
} catch (DuplicateMovieException e) {
view.duplicateException(newName);
}
}
}




















Now we need to add a setRating() method
to Movie. We won't test-drive that, as it will be a simple
mutator and it will be tested indirectly. In fact, I won't even
bother showing it here. Most IDEs will have a way to generate
accessors and mutators for instance variables.



Green bar. Now we can turn to the Swing layer.
We can take the same approach to editing the rating as to editing
the name: When a movie is selected, use a field to edit its value.
In this case, since the range is constrained, we'll use a combo
box.



We'll start by adding some ratings to the
fixture:




starWars = new Movie("Star Wars",5);
starTrek = new Movie("Star Trek",3);
stargate = new Movie("Stargate");






Green bar. All the tests still work. We can
continue.





Test 33: Selecting a movie from the
list updates the displayed rating



We'll start with a test to check that selecting
a movie in the list sets the rating selection combo box to the
appropriate value:




public void testSelectUpdatesRating() {
JListOperator movieList = new JListOperator(mainWindow);
JComboBoxOperator ratingCombo = new JComboBoxOperator(mainWindow);

movieList.clickOnItem(0, 1);
assertEquals("wrong rating from selecting starWars.",
6,
ratingCombo.getSelectedIndex());

movieList.clickOnItem(1, 1);
assertEquals("wrong rating from selecting starTrek.",
4,
ratingCombo.getSelectedIndex());

movieList.clickOnItem(2, 1);
assertEquals("wrong rating from selecting stargate.",
0,
ratingCombo.getSelectedIndex());
}






Now we need to make it pass. We need a combo box
in the GUI and methods to set and get its value:




private JComboBox ratingField = null;

public void setRatingField(int i) {
ratingField.setSelectedIndex(i);
}













public int getRatingField() {
return ratingField.getSelectedIndex();
}

public void init() {
setTitle();
setLayout();
initList();
initField();
initRatingCombo();
initAddButton();
initUpdateButton();
pack();
}

private void initRatingCombo() {
ratingField = new JComboBox(CustomMovieListRenderer.icons());
getContentPane().add(ratingField);
}






Green bar. OK. We talk to the customer. We need
to be able to select a movie, change the rating, and click update.
We try it. Uh oh. We get the duplicate
movie
message. Hmmm... think think... talk talk... what we
need to do is raise that error only if the update makes the
selected movie equal to a different movie in the list.



We need to go back to
TestMovieListEditor and create a test to drive that
behavior:




public void testUpdatingWithSameName() {
Vector newMovies = new Vector();
newMovies.add(starWars);
newMovies.add(new Movie("Star Trek",5));
newMovies.add(stargate);

mockView.setMovies(movies);
control.setVoidCallable(1);

mockView.setNameField("Star Trek");
control.setVoidCallable(1);
mockView.setRatingField(4);
control.setVoidCallable();

mockView.getNameField();
control.setReturnValue("Star Trek",1);
mockView.getRatingField();
control.setReturnValue(6, 1);

mockView.setMovies(newMovies);
control.setVoidCallable(1);

control.activate();

MovieListEditor editor = new MovieListEditor(movieList,mockView);
editor.select(1);
editor.update();












control.verify();
}






Now we can tweak the
MovieListEditor.update() method to make the bar green
again:




public void update() {
if (selectedMovie != null) {
String newName = view.getNameField();
if (selectedMovie.getName().equals(newName)) {
updateMovie();
} else {

try {
movies.rename(selectedMovie, newName);
updateMovie();
} catch (DuplicateMovieException e) {
view.duplicateException(newName);
}
}
}
}

private void updateMovie() {
selectedMovie.setRating(view.getRatingField() - 1);
updateMovieList();
}






Notice that we did some refactoring while we
were there.





Test 34: Updating a movie in the GUI
changes its rating if a different rating was selected for it, and
updates the display accordingly



Now back to the Swing GUI. We need a test that
selects a movie, changes the rating, and updates:




public void testUpdateRating() {
JListOperator movieList = new JListOperator(mainWindow);
JComboBoxOperator ratingCombo = new JComboBoxOperator(mainWindow);
movieList.clickOnItem(0, 1);
ratingCombo.setSelectedIndex(4);

JButtonOperator updateButton = new JButtonOperator(mainWindow, "Update");
updateButton.pushNoBlock();
movieList.clickOnItem(1, 1);
movieList.clickOnItem(0, 1);
assertEquals("updating should have changed rating.",
4,
ratingCombo.getSelectedIndex());
}






Green bar. Life is good. Finally, Figure 13.2 shows the current
state of the GUI.







Figure 13.2. GUI with editable
ratings.




























































Amazon






No comments:

Post a Comment