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:
selecting a movie will put its name in the text field
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!
|
No comments:
Post a Comment