Test-Driven Development in GUIs

Hi,

Anyone have any tips for writing GUIs in Ruby (using Tk, for example)
using a TDD approach?

A google search gave me
http://approximity.com/ruby/rubytk.html#testfirst, which claims that
some guy is writing a book on TDD for GUIs, using Ruby/Tk as preferred
language/toolkit. That section links to
http://www.c2.com/cgi/wiki?TestFirstUserInterfaces, while informative,
doesn't contain any information that I can see on a book.

Say, for the purposes of this discussion, say that you had a small Tk
application that monitored the status of ten websites. If the website
was up and was active and performing well, the GUI would display a
"Working!" message and perhaps showed data about the site (latency,
bandwidth, whatever). The GUI could also display graphs of
uptime/downtime, view history reports, and moderately complex GUI
stuff like that.

How would you go about testing that GUI application? There's a strong
chance that if I get some good ideas from my head and from this
discussion, that I'd write that application and also write a HOWTO on
TDD with GUIs.

Thanks,
Joe

TDDing UI is hard, because it's a side-effect, rather than a return value or a parameter. I don't know the half of it. Nor, for that matter, know how to use Tk. I present to you a "Hello world" example.

require 'test/unit'

class Greeter
  def self.greet
    puts 'Hello, world!'
  end
end

class MockGreeter < Greeter
  class << self
    attr_reader :out
    def puts(*args)
      @out = @out ? [] : args
    end
  end
end

class TestGreeter < Test::Unit::TestCase
  def testGreet
    MockGreeter.greet
    assert_equal ['Hello, world!'], MockGreeter.out
  end
end

Subclass and override puts. Dependency injection doesn't help here. You can have greet take a Module as a parameter, and call module.puts, but you still need to test the container, and make sure it's passing Kernel to the greet method.

Sorry, that's the short version. I gotta go.

Devin

Here's a very simple example of a quick UI app that's unit tested.
Comments appreciated.

If you run the application like 'ruby file.rb test', the tests will
get run. If you run it like 'ruby file.rb', the gui will be
displayed. In a real application, the tests would be in their own
file, of course.

As I do more research, I'll post more examples.

require 'tk'
require 'test/unit'

class App
  attr_accessor :sum, :inc_button, :display_button, :power_2_button

  def initialize
    @sum = 0

    root = TkRoot.new :title => 'my app', :geometry => '300x300'

    @inc_button = TkButton.new(root,
                             :text => "Increment!",
                             :command => proc { inc }
                             )
    @inc_button.pack :expand => true

    @power_2_button = TkButton.new(root,
                                  :text => "Power of 2",
                                  :command => proc { power2 }
                                  )
    @power_2_button.pack :expand => true
  end

  def inc
    @sum += 1
    display
  end

  def power2
    @sum *= @sum
    display
  end

  def display
    puts @sum
  end
end

class TestApp < Test::Unit::TestCase
  def testInc
    app = App.new

    assert_equal 0, app.sum

    app.inc_button.invoke
    assert_equal 1, app.sum

    app.inc_button.invoke
    assert_equal 2, app.sum
  end

  def testPower2
    app = App.new

    assert_equal 0, app.sum
    
    app.power_2_button.invoke
    assert_equal 0, app.sum

    app.inc_button.invoke
    assert_equal 1, app.sum

    app.power_2_button.invoke
    assert_equal 1, app.sum

    app.inc_button.invoke
    assert_equal 2, app.sum

    app.power_2_button.invoke
    app.power_2_button.invoke
    assert_equal 16, app.sum
  end
end

if ARGV[0] != 'test'
  App.new
  Tk.mainloop
  exit
end

···

On 6/7/05, Joe Van Dyk <joevandyk@gmail.com> wrote:

Hi,

Anyone have any tips for writing GUIs in Ruby (using Tk, for example)
using a TDD approach?

A google search gave me
Mitom Tv Bóng Đá Trực Tiếp - Kênh Ttbđ Mitomtv Không Giật Lag-Full Hd Blv, which claims that
some guy is writing a book on TDD for GUIs, using Ruby/Tk as preferred
language/toolkit. That section links to
http://www.c2.com/cgi/wiki?TestFirstUserInterfaces, while informative,
doesn't contain any information that I can see on a book.

Say, for the purposes of this discussion, say that you had a small Tk
application that monitored the status of ten websites. If the website
was up and was active and performing well, the GUI would display a
"Working!" message and perhaps showed data about the site (latency,
bandwidth, whatever). The GUI could also display graphs of
uptime/downtime, view history reports, and moderately complex GUI
stuff like that.

How would you go about testing that GUI application? There's a strong
chance that if I get some good ideas from my head and from this
discussion, that I'd write that application and also write a HOWTO on
TDD with GUIs.

Thanks,
Joe

Here's another iteration, this time the result is being displayed to a
TkLabel. The tests check to see if the label has the correct value.

So, when testing GUIs, is it good practice to have all the (tested)
widgets be available through attr_accessor (or attr_readers)? How
else can you test them?

require 'tk'
require 'test/unit'

class App
  attr_accessor :sum, :inc_button, :display_button, :power_2_button,
  :output_label

  def initialize
    @sum = TkVariable.new
    @sum.value = 0

    root = TkRoot.new :title => 'my app'

    @inc_button = TkButton.new root
    @inc_button.text = "Increment"
    @inc_button.command = proc { inc }
    @inc_button.pack :expand => true

    @power_2_button = TkButton.new(root)
    @power_2_button.text = "Power of 2"
    @power_2_button.command = proc { power2 }
    @power_2_button.pack :expand => true

    description_label = TkLabel.new
    description_label.text = "Result:"
    description_label.pack :expand => true
    @output_label = TkLabel.new
    @output_label.textvariable = @sum
    @output_label.pack :expand => true
  end

  def inc
    @sum.value = @sum.value.to_i + 1
  end

  def power2
    @sum.value = @sum.value.to_i * @sum.value.to_i
  end
end

class TestApp < Test::Unit::TestCase
  def result(app)
    app.output_label.text.to_i
  end

  def testInc
    app = App.new

    assert_equal 0, result(app)

    app.inc_button.invoke
    assert_equal 1, result(app)

    app.inc_button.invoke
    assert_equal 2, result(app)
  end

  def testPower2
    app = App.new

    assert_equal 0, result(app)

    app.power_2_button.invoke
    assert_equal 0, result(app)

    app.inc_button.invoke
    assert_equal 1, result(app)

    app.power_2_button.invoke
    assert_equal 1, result(app)

    app.inc_button.invoke
    assert_equal 2, result(app)

    app.power_2_button.invoke
    app.power_2_button.invoke
    assert_equal 16, result(app)
  end
end

if ARGV[0] != 'test'
  App.new
  Tk.mainloop
  exit
end

···

On 6/8/05, Joe Van Dyk <joevandyk@gmail.com> wrote:

On 6/7/05, Joe Van Dyk <joevandyk@gmail.com> wrote:
> Hi,
>
> Anyone have any tips for writing GUIs in Ruby (using Tk, for example)
> using a TDD approach?
>
> A google search gave me
> Mitom Tv Bóng Đá Trực Tiếp - Kênh Ttbđ Mitomtv Không Giật Lag-Full Hd Blv, which claims that
> some guy is writing a book on TDD for GUIs, using Ruby/Tk as preferred
> language/toolkit. That section links to
> http://www.c2.com/cgi/wiki?TestFirstUserInterfaces, while informative,
> doesn't contain any information that I can see on a book.
>
> Say, for the purposes of this discussion, say that you had a small Tk
> application that monitored the status of ten websites. If the website
> was up and was active and performing well, the GUI would display a
> "Working!" message and perhaps showed data about the site (latency,
> bandwidth, whatever). The GUI could also display graphs of
> uptime/downtime, view history reports, and moderately complex GUI
> stuff like that.
>
> How would you go about testing that GUI application? There's a strong
> chance that if I get some good ideas from my head and from this
> discussion, that I'd write that application and also write a HOWTO on
> TDD with GUIs.
>
> Thanks,
> Joe
>

Here's a very simple example of a quick UI app that's unit tested.
Comments appreciated.

If you run the application like 'ruby file.rb test', the tests will
get run. If you run it like 'ruby file.rb', the gui will be
displayed. In a real application, the tests would be in their own
file, of course.

As I do more research, I'll post more examples.

Tk question: How can I tell, via code, if a button is actually being
displayed on the screen?

Say buttons are being created based on data from a configuration file.
If I have a method that reads the config file and generates buttons,
how can I make sure those buttons are actually going to be displayed
on the screen?

I just ran into this problem when I forgot to 'pack' some buttons,
where 'packing' in Tk makes them visible/active on the screen. But I
don't know how to test for that.

···

On 6/8/05, Joe Van Dyk <joevandyk@gmail.com> wrote:

On 6/8/05, Joe Van Dyk <joevandyk@gmail.com> wrote:
> On 6/7/05, Joe Van Dyk <joevandyk@gmail.com> wrote:
> > Hi,
> >
> > Anyone have any tips for writing GUIs in Ruby (using Tk, for example)
> > using a TDD approach?

Joe Van Dyk wrote:

Tk question: How can I tell, via code, if a button is actually being
displayed on the screen?

Look at my original response to you. Your unit tests don't need to test that Tk is doing its job (hopefully the Tk people are doing that, but seconding that, maybe your functional end-to-end tests are). Your unit tests only need to test that you are making the right calls to the right Tk methods.

Devin

Message-ID: <c715e640506081559351a9a28@mail.gmail.com>

Tk question: How can I tell, via code, if a button is actually being
displayed on the screen?

Does TkWinfo.mapped?(win) or TkWindow#winfo_mapped? satisfy you?

···

From: Joe Van Dyk <joevandyk@gmail.com>
Subject: Re: Test-Driven Development in GUIs
Date: Thu, 9 Jun 2005 07:59:31 +0900
--
Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)