Home > Archive > Extreme Programming > June 2004 > TDD Question concerning GUIs
You are viewing an archived Text-only version of the thread.
To view this thread in it's original format and/or if you want to reply to
this thread please [click here]
| Author |
TDD Question concerning GUIs
|
|
|
| All,
A situation that I have run into several times with trying out TDD (in
the .NET environment) is the following:
In a controller, I have a method that is called from the view (a Windows
form, fwiw) when a user event occurs. In response, it builds a list of
items using a separate class, then passes the list to a different form.
Finally, it displays the form. Something like
void OnSomethingHappening()
{
ListBuilder listBuilder = new ListBuilder(aParam);
ArrayList list = listBuilder.getList();
NewForm nf = new NewForm(list);
nf.ShowDialog();
}
I've created NUnit tests for the ListBuilder class, but of course,
testing OnSomethingHappening causes the form to show up and wait for
user action making the test unusable. The choices seem to be
a) don't test OnSom... at all
b) split it into two functions, both public, so that
OnSomethingHappening calls the other function...then test the other function
c) spend way too much time figuring out how to put a mock NewForm in the mix
Thoughts?
TIA
Keith
| |
|
| Keith wrote:
> void OnSomethingHappening()
> {
> ListBuilder listBuilder = new ListBuilder(aParam);
> ArrayList list = listBuilder.getList();
> NewForm nf = new NewForm(list);
> nf.ShowDialog();
> }
>
> I've created NUnit tests for the ListBuilder class, but of course,
> testing OnSomethingHappening causes the form to show up and wait for
> user action making the test unusable. The choices seem to be
> c) spend way too much time figuring out how to put a mock NewForm in the
mix
If that takes too much time, then your tests and code are not sufficiently
decoupled.
But feel free to add a method that only tests call, such as "set dialog
class".
> a) don't test OnSom... at all
If you have bugs in your GUI, they will be in the layer closest to your end
users.
> b) split it into two functions, both public, so that
> OnSomethingHappening calls the other function...then test the other
function
Test both functions.
I don't know .NET yet, but if ShowDialog() does not block, then try this:
NewForm OnSomethingHappening()
{
ListBuilder listBuilder = new ListBuilder(aParam);
ArrayList list = listBuilder.getList();
NewForm nf = new NewForm(list);
nf.ShowDialog();
return nf;
}
Now the test case can capture the NewForm return value and immediately
closes it. Because ShowDialog() should only buffer the internal WM_PAINT
command, not dispatch it, killing a recently shown dialog should not even
flicker the screen.
If ShowDialog() blocks, switch to a method that does not block, and then
disable the parent window while the dialog is up. The SDK treats this the
same as a modal dialog, because most modal dialogs are secretly modeless
anyway. Event driven architectures are (almost) always better.
--
Phlip
http://industrialxp.org/community/bin/view/Main/TestFirstUserInterfaces
| |
|
| Phlip wrote:
> Keith wrote:
>
>
>
>
>
> mix
>
> If that takes too much time, then your tests and code are not sufficiently
> decoupled.
>
> But feel free to add a method that only tests call, such as "set dialog
> class".
>
Creating a mock for NewForm is not the problem - the problem is how to
get the OnSom... function to create the mock and not the real form. I've
seen some discussions around injection (can't remember where) that might
address it. In general, though, there seems to be a decreasing ROI when
it comes to trying to manipulate this type of code to accomodate unit
testing.
>
>
>
> If you have bugs in your GUI, they will be in the layer closest to your end
> users.
>
Agreed. Going without GUI testing is not a possibility. I don't agree
with those that think that all tests should be automated, in particular
GUI testing. There is just too much that cannot really be tested without
putting eyes on the UI.
>
>
> function
>
> Test both functions.
>
> I don't know .NET yet, but if ShowDialog() does not block, then try this:
>
> NewForm OnSomethingHappening()
> {
> ListBuilder listBuilder = new ListBuilder(aParam);
> ArrayList list = listBuilder.getList();
> NewForm nf = new NewForm(list);
> nf.ShowDialog();
> return nf;
> }
>
> Now the test case can capture the NewForm return value and immediately
> closes it. Because ShowDialog() should only buffer the internal WM_PAINT
> command, not dispatch it, killing a recently shown dialog should not even
> flicker the screen.
>
> If ShowDialog() blocks, switch to a method that does not block, and then
> disable the parent window while the dialog is up. The SDK treats this the
> same as a modal dialog, because most modal dialogs are secretly modeless
> anyway. Event driven architectures are (almost) always better.
>
ShowDialog (the only real function in this example...) does indeed
block. The alternate, Show(), does not. Have not found a way to disable
the parent form. The obvious call (Enable = false) does not work, but it
is an interesting idea.
Keith
| |
| Robert C. Martin 2004-06-24, 1:22 am |
| On Tue, 22 Jun 2004 01:40:38 GMT, Keith <keith@notmyrealaddress.com>
wrote:
>All,
>A situation that I have run into several times with trying out TDD (in
>the .NET environment) is the following:
>
>In a controller, I have a method that is called from the view (a Windows
>form, fwiw) when a user event occurs. In response, it builds a list of
>items using a separate class, then passes the list to a different form.
>Finally, it displays the form. Something like
>
>void OnSomethingHappening()
>{
> ListBuilder listBuilder = new ListBuilder(aParam);
> ArrayList list = listBuilder.getList();
> NewForm nf = new NewForm(list);
> nf.ShowDialog();
>}
>
>I've created NUnit tests for the ListBuilder class, but of course,
>testing OnSomethingHappening causes the form to show up and wait for
>user action making the test unusable. The choices seem to be
>a) don't test OnSom... at all
>b) split it into two functions, both public, so that
>OnSomethingHappening calls the other function...then test the other function
>c) spend way too much time figuring out how to put a mock NewForm in the mix
void OnSomethingHappening()
{
ListBuilder listBuilder = new ListBuilder(aParam);
ArrayList list = listBuilder.getList();
Form nf = FormMaker.MakeNewForm(...);
nf.Show();
}
public interface Form {
public virtual void Show();
}
Making a Mock never takes too much time. Debugging takes too much
time.
Never consider the time spent figuring out how to test something as
wasted.
Always consider the time spent debugging something as wasted.
-----
Robert C. Martin (Uncle Bob)
Object Mentor Inc.
unclebob @ objectmentor . com
800-338-6716
"The aim of science is not to open the door to infinite wisdom,
but to set a limit to infinite error."
-- Bertolt Brecht, Life of Galileo
| |
|
| Keith wrote:
> Creating a mock for NewForm is not the problem - the problem is how to
> get the OnSom... function to create the mock and not the real form. I've
> seen some discussions around injection (can't remember where) that might
> address it. In general, though, there seems to be a decreasing ROI when
> it comes to trying to manipulate this type of code to accomodate unit
> testing.
Uncle Bob addressed both questions. I would prefer to use the real things
without a mock, but his answer still applies: Research a little to learn to
use the real thing without letting it display on the screen or block the
test. And do ANYTHING to be able to run useful tests every 1~10 edits and
avoid debugging.
As you learn to test a GUI, your code learns. You add to your tests
re-usable functions that assist testing. When you figure out how to mock, or
how to un-block, you will write a function and re-use this in other tests.
Eventually you can develop GUI features and refactor them for a long time
without ever displaying a window.
> Agreed. Going without GUI testing is not a possibility. I don't agree
> with those that think that all tests should be automated, in particular
> GUI testing. There is just too much that cannot really be tested without
> putting eyes on the UI.
Remember that (in this newsgroup) the point is test-FIRST. None of this is
about retrofitting tests to existing GUIs.
The only way to shift the testing from last to first is to research those
reusable testing functions, and start reusing them.
Curiously, if you put a temporary statement into a test-case to reveal a
window under test, before destroying it, you can visually inspect it,
populated with test data. Adding these temporary visual inspections to a TDD
cycle prevents wasting time driving a GUI to a tested state manually, and
teaches which test statements are important. If a button unexpectedly moves
across the screen, use test-first to push it back, not the form-painter.
This cycle uses visual inspections to drive test case writing, and to
re-usable test function writing, leading directly to test-first.
> ShowDialog (the only real function in this example...) does indeed
> block. The alternate, Show(), does not. Have not found a way to disable
> the parent form. The obvious call (Enable = false) does not work, but it
> is an interesting idea.
In the underlying Win32 "SDK" library, one simulates modality with a
combination of ShowWindow() and disabling the parent. DoModal() is
programmer-hostile, and both production code and tests should do without it.
However, if Uncle Bob's mock gets you TDD-ing GUIs faster, use it.
--
Phlip
http://industrialxp.org/community/bin/view/Main/TestFirstUserInterfaces
| |
| Ronald E Jeffries 2004-06-24, 1:22 am |
| On Tue, 22 Jun 2004 05:22:53 GMT, "Phlip" <phlip_cpp@yahoo.com> wrote:
>Uncle Bob addressed both questions. I would prefer to use the real things
>without a mock, but his answer still applies: Research a little to learn to
>use the real thing without letting it display on the screen or block the
>test. And do ANYTHING to be able to run useful tests every 1~10 edits and
>avoid debugging.
>
>As you learn to test a GUI, your code learns. You add to your tests
>re-usable functions that assist testing. When you figure out how to mock, or
>how to un-block, you will write a function and re-use this in other tests.
>Eventually you can develop GUI features and refactor them for a long time
>without ever displaying a window.
This is good advice, but I'm not presently able to live up to it. In
..NET, I don't know how to do these things, and need some good
examples, not just encouragment to do it. Show me?
Thanks,
--
Ron Jeffries
www.XProgramming.com
I'm giving the best advice I have. You get to decide if it's true for you.
| |
| Richard Curzon 2004-06-30, 3:58 pm |
| Another approach is writing a whole lot LESS mock code, and use tools
that simulate the user better.
On windows I find John Robbins Tester code often is the magic
ingredient for simple effective tests.
Last version of the Robbins article:
msdn.microsoft.com/msdnmag/issues/02/03/Bugslayer/default.aspx
He uses a journal hook GUI driver, an ingenious simple object model
and syntax to drive keystrokes and mouse actions, and a nice tool to
record keystrokes so they can be reused via cut and paste.
More often though you'd write code to drive the playback, it has many
advantages and is still very simple.
There are caviats: some compound keystrokes don't work due to
"features" of windows journalling (e.g Control-Shift-End can't be
played back). The keystroke recorder generates ECMAscript or VB
script, which has to be transformed into .net languages.
On the plus side, VStudio is smart enough to translate VB script
syntax to VB.net syntax during the paste operation. Aside from the
key stroke recording, the object model supports a lot of System
commands, FindWindow commands, Notifications on Create/Destroy of
windows.
Richard Curzon
|
|
|
|
|