Tuesday, October 27, 2009

MOVIE RENAME GUI









































Prev don't be afraid of buying books Next






























MOVIE RENAME GUI






On the GUI, selecting
a movie fills in the text field with its name. An "Update" button
will update the movie name with the contents of the
field.





Now we must make the newly added rename
capability available on the GUI. The task calls for two things:











  1. selecting a movie will put its name in the text
    field











  2. clicking an Update button that will cause the
    selected movie to be renamed with what is in the text field.











Here are the required tests:






Test 14.
Indicating, to the logical layer, that a selection is made from the
list causes the view to be given a value for the name field, that
is, the selected movie's name.










Test 15.
Selecting from the list causes the name field to be filled in with
the selected movie's name.










Test 16. When
an update is requested, the selected movie is renamed to whatever
is answered by the view as the new name.










Test 17. When
the update button is pushed, the selected movie is renamed to
whatever is in the name field.











Let's tackle them one at a time, in the order
above. First, we need a test for filling in the text field with the
name of the selected movie. As before, we will work at the logic
level first, then the GUI.





Test 14: Indicating, to the logical
layer, that a selection is made from the list causes the view to be
given a value for the name field, that is, the selected movie's
name




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

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

control.activate();

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

control.verify();
}

















We need to stub two methods to get this to
compile. First, we need to add setNewName() to
MovieListEditorView:




void setNewName(String string);






Second, we need to add select() to
MovieListEditor:




public void select(int i) {
}






Now it compiles and fails: the expected call to
setNewName() didn't happen. That needs to happen in
response to calling select() on the
MovieListEditor. So we add enough to select() to
get the test to pass:




public void select(int i) {
view.setNewName("Star Trek");
}






Green bar! But look at that string literal.
Yuck! That duplicates the name of the second movie in the list we
set up. Let's get it from there:




public void select(int i) {
view.setNewName(movies.getMovie(i).getName());
}






In order to get this to compile we need to add
getMovie() to MovieList:




public Movie getMovie(int i) {
return null;
}






Oops! That breaks our test. But it's the
null that getMovie() is returning that is causing
the problem. We'll have to expand on that:




public Movie getMovie(int i) {
return (Movie) movies.get(i);
}






OK, our test is working again. Just to be sure,
let's add to our test. We'll make it select another movie in the
list and make sure that that name is sent to the view:




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

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












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

control.activate();

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

control.verify();
}






Compile, run, green bar. That verifies that the
work we just did really does work for the general case. Now we're
done with the logical layer; time to turn our attention to the
GUI.





Test 15: Selecting from the list
causes the name field to be filled in with the selected movie's
name



This will be fairly simple since it doesn't
involve adding additional components. Here's the test (in
TestGUI):




public void testSelecting() {
mainWindow = new JFrameOperator("Movie List");
MovieListEditor editor =
new MovieListEditor(movieList,
(SwingMovieListEditorView)mainWindow.getWindow());
JListOperator movieList = new JListOperator(mainWindow);
movieList.clickOnItem(1, 1);

JTextFieldOperator newMovieField = newJTextFieldOperator(mainWindow);
assertEquals("wrong text from selection.",
"Star Trek",
newMovieField.getText());
}






First, we need to add setNewName() to
SwingMovieListEditorView to conform to the change in the
interface MovieListEditorView:




public void setNewName(String newName) {
}







Now it compiles, and fails. We need to add
some code to hook up list selection to the underlying logic (while
we're there and dealing with selection, we'll set the selection
mode):




private void initList() {
movieList = new JList(new Vector());
movieList.setSelectionMode(ListSelectionModel.SINGLE SELECTION);
movieList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
myEditor.select(movieList.getSelectedIndex());
}
});
JScrollPane scroller = new JScrollPane(movieList,
ScrollPaneConstants.VERTICAL SCROLLBAR ALWAYS,
ScrollPaneConstants.HORIZONTAL SCROLLBAR NEVER);
getContentPane().add(scroller);
}






Compile, run, red bar. One more thing to do. We
need to fill in the setNewName method so that it puts its
argument into the text field:




public void setNewName(String newName) {
movieField.setText(newName);
}






Compile, run, green bar! Now we need to add the
Update button.





Test 16: When an update is requested,
the selected movie is renamed to whatever is answered by the view
as the new name



We will start back at the logic layer with a
test:




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

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

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

mockView.getNewName();
control.setReturnValue("Star Trek I",1);

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

control.activate();









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

control.verify();
}






To compile we need to add a stub to
MovieListEditor:




public void update() {
}






The test fails due to the expected calls to
getNewName() and setMovies() not happening. This
is hardly surprising since update() does nothing. Let's
add some code to it:




public void update() {
if (selectedMovie != null) {
selectedMovie.rename(view.getNewName());
view.setMovies(new Vector(movies.getMovies()));
}
}






We haven't defined selectedMovie yet.
We can just grab the Movie whose name we fetch in
select (keeping in mind that we will get calls of
select(-1) due to the way JList works):




private Movie selectedMovie;

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





















You will find that there is a significant amount
of code that you will write in GUI classes that isn't strictly
test-driven. This is, in my opinion, an unavoidable aspect of
developing a GUI. The code I'm referring to is the
framework-related details. An example here is handling list
selections with index -1 denoting the clearing of all selection,
dealing with layout managers, and other visual aspects of the
interface. These things are the subject of other types of testing
that don't relate strictly to the functional aspects of the
application. At the functional level, all we really care about is
that the required components are present and that they operate as
required. Once the tests are in place, you can tweak the visual
aspects, confident that you are not breaking the functionality.
It's a lot like optimization: get it right, then make it look
good.
















Now it compiles, and we get a green bar!





Test 17: When the update button is
pushed, the selected movie is renamed to whatever is in the name
field



Now we can move on to the Swing layer. This will
involve adding a new button for updating. Here's the test (notice
the click on the 0th list item to reset the text field):




public void testUpdating() {
mainWindow = new JFrameOperator("Movie List");
MovieListEditor editor =
new MovieListEditor(movieList,
(SwingMovieListEditorView) mainWindow.getWindow());

JListOperator movieList = new JListOperator(mainWindow);
movieList.clickOnItem(1, 1);

JTextFieldOperator newMovieField = new JTextFieldOperator(mainWindow);
newMovieField.enterText("Star Trek I");

JButtonOperator updateButton = new JButtonOperator(mainWindow, "Update");
updateButton.doClick();

movieList.clickOnItem(0, 1);
movieList.clickOnItem(1, 1);
assertEquals("Movie should have been renamed.",
"Star Trek I",
newMovieField.getText());
}






This compiles, but fails, because there isn't an
update button in the GUI yet. We add it:




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

private void initUpdateButton() {
JButton updateButton = new JButton("Update");
getContentPane().add(updateButton);
}







Now the test fails due to an assertion
failure: the name isn't being changed. We next need to hook up the
update button to the underlying editor:




updateButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
myEditor.update();
}
});






Green bar! Now have a look. Is there anything
that needs to be cleaned up?



In MovieListEditor there is some
duplication between methods that update the movie list on the
interface, specifically, the line:




view.setMovies(new Vector(movies.getMovies()));






This appears in three different methods; time to
refactor, to be sure. We'll use Extract Method to put it in its own
method:




private void updateMovieList() {
view.setMovies(new Vector(movies.getMovies()));
}






In each method where that line occurred we'll
replace it by a call to update-MovieList();for
example:




public void update() {
if (selectedMovie != null) {
selectedMovie.rename(view.getNewName());
updateMovieList();
}
}






One last thing we should do is add a
main() method to our Swing GUI class so that we can run
our application stand-alone:




public static void main(String[ ] args) {
SwingMovieListEditorView window = new SwingMovieListEditorView();
window.init();
window.show();
MovieList list = new MovieList();
MovieListEditor editor = new MovieListEditor(list, window);
}






Now there's some duplication between this method
and start() in the same class:




public static void start() {
SwingMovieListEditorView window = new SwingMovieListEditorView();
window.init();
window.show();
}















Since, in this case, all of the code in
start() is included in main(),we can replace
those lines in main() with a call to start() if
we have start() return the window it creates:




public static SwingMovieListEditorView start() {
SwingMovieListEditorView window = new SwingMovieListEditorView();
window.init();
window.show();
return window;
}

public static void main(String[ ] args) {
SwingMovieListEditorView window = SwingMovieListEditorView.start();
MovieList list = new MovieList();
MovieListEditor editor = new MovieListEditor(list, window);
}






Compile, run all the tests, green bar. No
worries!















































Amazon






No comments: