How to duck type? - the psychology of static typing in Ruby

G

Gavin Sinclair

How about a delegator to your Customer class, with some extensions for
distributed computing?
That's a very common one that can be gotten wrong: Will your type checks
work with a dRb proxied object?

That's a good question. And the answer, I guess, is no.

Gavin
 
M

Marek Janukowicz

No one requires you to do this. Instead, you do this if you feel
uncomfortable with your code. After a while, you stop feeling
uncomfortable, and realize that all this type discussion is simply a
red herring, and get back to writing proper code and the tests to test
it. A few months after that, you're surprised when you realize that you
haven't had a single type-related error.

This is simple a question of experience. When you first start out, you
feel exposed because Ruby doesn't let you add type annotations to
variables (I know I did). Knowing that it worked OK in Smalltalk, I
made the conscious decision not to fight this, but instead to see how
it worked just doing it the Ruby way. It was hard at first, and I kept
fighting some inner demon who wanted me to add "kind_of?" calls
everywhere. But after a while, I realized that my code was no less
accurate than it was in Java, and that I didn't really need the type
information. Once I overcame that fear, I suddenly realized that I
could also get a lot of benefit from the flexibility: the realization
that type != class, but rather that type equals capabilities was a
revelation, and it has fundamentally changed the way I code.

However, I honestly believe that there is no short cut on this path:
the only way to come to understand the true nature of typing in Ruby is
to write a bunch of code the Ruby way, and to gain your own experience
(and form your own opinions). Unit tests are a safety net that can be
used while crossing that wire. Once across, you'll be able to form your
own opinions about the effectiveness of unit tests for type-related
checking.

OK, but I personally find it difficult to write unit tests for some kind
of programs (especially web and GUI applications). I don't see any
testing frameworks for such programs laying around. I really would like
to follow the path you presented above, but how can I do this without
being able to write tests? You said before you mostly write web
applications - could you share your experience regarding testing them
with us?
 
D

Dave Thomas

OK, but I personally find it difficult to write unit tests for some
kind
of programs (especially web and GUI applications). I don't see any
testing frameworks for such programs laying around. I really would like
to follow the path you presented above, but how can I do this without
being able to write tests? You said before you mostly write web
applications - could you share your experience regarding testing them
with us?

I'm careful to separate the application and business object code from
the UI side, making it easier to test. I also use a lot of
meta-programming, which cuts down on the amount of stuff where testing
is needed.

(I also have a dirty secret when it comes to unit testing, but I'm not
going to share it here... :))

Cheers

Dave
 
C

Chris Morris

Dave said:
(I also have a dirty secret when it comes to unit testing, but I'm not
going to share it here... :))

Where and when will the sharing be unveiled? :)
 
M

Marek Janukowicz

I'm careful to separate the application and business object code from
the UI side, making it easier to test.

Such a separation is rather obvious. The business logic is rather easy
to test, but in my experience most bugs occur on the UI side (especially
in the connection layer between UI and business logic). How do you test
UI?
I also use a lot of meta-programming, which cuts down on the amount of
stuff where testing is needed.

Could you elaborate some (maybe trivial) example? I know what
meta-programming is, but I don't know how can it be employed in eg. web
application testing.
(I also have a dirty secret when it comes to unit testing, but I'm not
going to share it here... :))

That's really a pity :)
 
H

Hal Fulton

Marek said:
Such a separation is rather obvious. The business logic is rather easy
to test, but in my experience most bugs occur on the UI side (especially
in the connection layer between UI and business logic). How do you test
UI?

UI is often hard, especially GUI.

But I ran into an idea the other day that I was unfamiliar with.

'Expect' is sometimes used to wrap a GUI around a text interface --
spawn the process and interact with it.

Of course, that in itself is non-trivial perhaps.

But a text interface is easier to test, especially with Expect.


Hal
 
S

Sean Russell

I'm posting a lot on this topic because I think we have people
approaching the issue from two different directions.

John Carter said:
The tighter you constrain the interface, the less reusable it is. In the
example I mentioned, the programmers had ruined the polymorphic behaviour
of the library by internally squashing everything into two byte int's. If
they hadn't done that, polymorphism would have allowed the library to work
on _anything_ that inherited from Number. Including the floating point
types I needed.

Inferrence duck-type checking doesn't constrain the interface
significantly.

Consider:

def meth( arg )
arg.each { |x| ... }
end

Now, the type checker knows that arg *must* implement #each(), and
that each() should accept a closure, right? So if call this method
from somewhere else with:

...
othermeth( 2
meth( 5 )
...

the checker should be able to warn me that this is probably not going
to work. I'm imagining something like:

$ ruby -c myprog.rb
myprog.rb:53: syntax error
myprog.rb:54: ducktype warning: "5" does not implement each()

An important aspect is that "-c" would check all of the code, not just
the parts that are more commonly used. It will catch things that are
missed by unit tests. It will find potential errors in your program
*before* you go into production. It will cure cancer, clean the air,
and bring peace to the Middle East.

I don't want to have to declare variables any more than anybody else;
what I want is a smarter "-c" code checker to find potential errors.

--- SER
 
J

James Britt

Hal said:
UI is often hard, especially GUI.

But I ran into an idea the other day that I was unfamiliar with.

'Expect' is sometimes used to wrap a GUI around a text interface --
spawn the process and interact with it.

Of course, that in itself is non-trivial perhaps.

But a text interface is easier to test, especially with Expect.

I've been using Ruby at my current job to test a web app via Internet
Explorer with the clIEController [0].

The downside is it only scripts IE, and (to the best of my knowledge),
you cannot get to the raw markup returned to the scripted browser, so it
doesn't help you verify, for example, that each page is rendered as you
expect.

I've hacked some code for the controller that walks the IE HTML node
tree and emits proper XML (not actual XHTML, though) so that one can run
xpath calls, via REXML, against the markup to see that certain data is
on the page, or check that a page fetch didn't return an error page.

If nothing else, it saves *much* time clicking through pages, filling in
forms, clicking submit, etc. My test script will spawn a score of
threads; browsers pop up all over the place, and I test a dozen
pages/site paths at a time.


James

[0] http://www.clabs.org/wtr/index.cgi?page=/ClIEController
 
D

Dave Thomas

Such a separation is rather obvious.
Sorry

The business logic is rather easy
to test, but in my experience most bugs occur on the UI side
(especially
in the connection layer between UI and business logic). How do you test
UI?

I don't unit test it. To be honest, I've never found it worth while. I
spend far more time maintaining the tests than the time I save because
they exist.

And in all this, tested or not, I very, very rarely see the wrong type
of object being used. Given that this is a thread object static typing,
that seems relevant... :)
Could you elaborate some (maybe trivial) example? I know what
meta-programming is, but I don't know how can it be employed in eg. web
application testing.

For example, all my business objects use database wrapped objects to do
the database access. All these are generated on the fly, and so I only
have to test one generator, rather than 50 individual classes.


Cheers

Dave
 
C

Charles Miller

(I also have a dirty secret when it comes to unit testing, but I'm not
going to share it here... :))

You tease, you.

I think everyone has their dirty unit testing secrets. Somewhere, in
some corner of reality, there is the Platonic ideal test suite. All our
test suites are flawed reflections of that ideal. However, by aiming at
that perfect suite, we at least come closer than if our sights had been
set lower.

C
 
J

John Carter

I'm posting a lot on this topic because I think we have people
approaching the issue from two different directions.

I like what you are saying, it is an excellent idea. In fact I like it
so much, because it is one of my own, (although we probably came to it
entirely independently).

My pet hobby over the last several years has been to design a postfix,
linear logic combination of Joy and Ruby that does exactly what you
are suggesting.
Inferrence duck-type checking doesn't constrain the interface
significantly.

Consider:

def meth( arg )
arg.each { |x| ... }
end

Now, the type checker knows that arg *must* implement #each(), and
that each() should accept a closure, right? So if call this method
from somewhere else with:


I immediately see two problems with implementation though...

a) Ruby is too dynamic, the value it is called with may well gain the
method between compilation and execution of that line. A case in
point is the 'cgi' module which won't just break under your ruling, it will
shatter.

b) Ruby has a remarkbly "narrow" (local) view of the world as it
compiles, to implement your suggest it would require a more global
view, if not multiple passes. This "local" view is important, it
keeps Ruby simple and fast.

Perhaps your suggestion should be implemented as a standalone "lint"
rather than as part of Ruby.



John Carter Phone : (64)(3) 358 6639
Tait Electronics Fax : (64)(3) 359 4632
PO Box 1645 Christchurch Email : (e-mail address removed)
New Zealand

The universe is absolutely plastered with the dashed lines exactly one
space long.
 
P

Paul

James - can you make this additional code available?

Also, when you spawn multiple browsers, did you investigate which
process they are in. When I have tried this they all live in the same
process, and therefore share cookies, so cant be used for testing site
that use cookies.

Thanks

Paul
 
D

Dave Burt

I wonder: does (Explorer -> Tools -> Folder Options -> View tab -> check
"Launch folder windows in a a separate process") affect IE and fix that
problem?
 
D

David Heinemeier Hansson

Rails[1] provide access to the rendered template _without_ going
through a browser, which can then be queried through XPath (if the
template uses XHTML). It can also provide access to the attributes
assigned to make the template. And even to check which template was
used.

This is done by using mock requests and responses and works extremely
well. It's considerably faster than going through HTTP for every
request (which is often what slows down functional/acceptance testing
and make them run for hours). It also gives you the opportunity to set
and check conditions through code that might have been incovenient to
generate through regular interfacing. Basically mocking.

So you have the opportunity to either XPath query the final XHTML or
examine the variables that the template is supposed to be generated
from. The latter is often a lot easier and catches most errors (by
basically just testing controller and model) that computers are good at
(comparing objects) and leaves the stuff that computers are less good
at it human testing (does this look right?).

[1] Rails is that web-application framework that I've been talking
about for far too long without sharing much code yet ;)
--
David Heinemeier Hansson,
http://www.instiki.org/ -- A No-Step-Three Wiki in Ruby
http://www.basecamphq.com/ -- Web-based Project Management
http://www.loudthinking.com/ -- Broadcasting Brain
http://www.nextangle.com/ -- Development & Consulting Services
 
K

Kirk Haines

Rails[1] provide access to the rendered template _without_ going
through a browser, which can then be queried through XPath (if the
template uses XHTML). It can also provide access to the attributes
assigned to make the template. And even to check which template was
used.

This is done by using mock requests and responses and works
extremely well. It's considerably faster than going through HTTP for
every request (which is often what slows down functional/acceptance
testing and make them run for hours). It also gives you the
opportunity to set and check conditions through code that might have
been incovenient to generate through regular interfacing. Basically
mocking.

Iowa is like this, as well. Iowa applications operate off of an
Iowa::Request, a class that looks a lot like Apache::Request but is
marshallable. It's easy to build requests, pass them to the application,
and get responses back, making it very easy to write scripts to test drive
applications and check responses.


Kirk Haines
 
J

James Britt

David said:
Rails[1] provide access to the rendered template _without_ going through
a browser, which can then be queried through XPath (if the template uses
XHTML). It can also provide access to the attributes assigned to make
the template. And even to check which template was used.

I was testing a site built with JSP and similar crap, running under
WebLogic. I needed to be sure that the user was getting what I
intended, so the test code had to make requests against the web server.

Also,out users are suing IE, so scripting the actual browser is handy
for checking that the javascript stuff worked.



James
 
S

Sean Russell

John Carter said:
I immediately see two problems with implementation though...

a) Ruby is too dynamic, the value it is called with may well gain the
method between compilation and execution of that line. A case in
point is the 'cgi' module which won't just break under your ruling, it will
shatter.

I'll have to take a look at the internals of CGI, but I agree that
inferrence typing won't catch everything, and will generate some false
warnings. I argue, however, that these cases are more the exception
than the rule, and that this makes inferrence typing in Ruby useful.
b) Ruby has a remarkbly "narrow" (local) view of the world as it
compiles, to implement your suggest it would require a more global
view, if not multiple passes. This "local" view is important, it
keeps Ruby simple and fast.

Which is why I keep saying, over, and over, and over, that I want
inferrence typing to be tied to the use of "-c", not to the execution
of the code.

How is your postfix project coming along?

--- SER
 
J

James Britt

Paul said:
James - can you make this additional code available?

Sure, with the typical disclaimer that goes with
skunkworks ruby-coding-on-the-sly-deep-inside-a-java-house code.

The code at the end of this post works for most cases. It does not, for
example, trap or fix characters in an incorrect encoding, and skips
processing instructions (and probably CDATA sections, now that I think
of it). These omissions haven't been an issue for me, but YMMV and all
that. It builds useful XML (albeit with far more attributes than one
might ever need). Use at will, no warranties expressed or implied, not
suitable for running nuclear power plants or medical equipment, mumble
mumble mumble.
Also, when you spawn multiple browsers, did you investigate which
process they are in. When I have tried this they all live in the same
process, and therefore share cookies, so cant be used for testing site
that use cookies.

I paid no mind to separate processes. Cookies were shared, I'm sure,
though I suspect this may be due to how the cookies were set by they
server; I believe the cookies would be shared even if I started
independent browser sessions by hand.

James

# ============================================================
# Code to walk an HTML node tree made available through the
# clIEController class. It requires that the owner class have an
# attribute named @iec that points to an instance of the IEController
# class, like this:
# @iec = ClIEController.new( true )
# Once you've navigated to a URL, you get the XML using:
# xml = recursive_dump()
# You can pass the xml to an REXML document to run XPath against it.

#--==============================================================
#++
def recursive_dump( el=nil, indent=0 )
el ||= @iec.htmlNode
name = el.nodeName.downcase
space = " " * indent
name.strip!
s = "#{space}<#{name}"

if el.attributes
for at in el.attributes
s << " #{at.name}='#{escape(at.value)}'" if attribute_valid?(at)
end
end

s << ">\n"

for node in el.childNodes
if node.childNodes && node.childNodes.length > 0
s << recursive_dump( node, indent+1 )
elsif node.nodeName.downcase == "#text"
s << escape( node.data )
elsif node.nodeName.downcase == "#comment"
s << "<!-- " + node.data + " -->\n"
else
name2 = node.nodeName.downcase
space2 = " " * (indent + 1)
s << "#{space2}<#{name2}"
if node.attributes
for at in node.attributes
s << " #{at.name}='#{escape( at.value )}'" if attribute_valid?( at )
end
end
s << ">\n"
s << "#{space2}</#{name2}>\n"
end
end
s << el.nodeValue if el.nodeValue
s << "#{space}</#{name}>\n"
end

#--===================================================================
#++
def attribute_valid?( at )
return false if at.value.to_s == "null"
return false if at.value.to_s.strip.size < 1
return false if at.name.to_s =~ /xml:/i
true
end

#--====================================================================
#++
def escape( str )
s = str.gsub( /&/, "&amp;")
s.gsub!( /</, "&lt;")
s.gsub!( />/, "&gt;")
s.gsub!( /\'/, "&apos;")
s.gsub( /\"/, "&quot;")
end
 
D

Daniel Berger

James Britt said:
Hal said:
UI is often hard, especially GUI.

But I ran into an idea the other day that I was unfamiliar with.

'Expect' is sometimes used to wrap a GUI around a text interface --
spawn the process and interact with it.

Of course, that in itself is non-trivial perhaps.

But a text interface is easier to test, especially with Expect.

I've been using Ruby at my current job to test a web app via Internet
Explorer with the clIEController [0].

The downside is it only scripts IE, and (to the best of my knowledge),
you cannot get to the raw markup returned to the scripted browser, so it
doesn't help you verify, for example, that each page is rendered as you
expect.

I've hacked some code for the controller that walks the IE HTML node
tree and emits proper XML (not actual XHTML, though) so that one can run
xpath calls, via REXML, against the markup to see that certain data is
on the page, or check that a page fetch didn't return an error page.

If nothing else, it saves *much* time clicking through pages, filling in
forms, clicking submit, etc. My test script will spawn a score of
threads; browsers pop up all over the place, and I test a dozen
pages/site paths at a time.


James

[0] http://www.clabs.org/wtr/index.cgi?page=/ClIEController

Dunno if it's relavent, but perhaps a port of Perl's WWW::Mechanize
module is in order.

Regards,

Dan
 
P

Paul

replying to several posts at once:
WWW:Mechanize doesnt use the real browser - just http requests. This
is good for testing in some situations. I would love for someone to
write a ruby version of httpUnit (http://httpunit.sourceforge.net/) .

There is a method for controlling IE in perl - SAMIE -
http://samie.sourceforge.net/ The code here takes a different approach
to that of Chris Morris IEController. People familiar with Silk or
Visual Test may fell happier with this.

As for the post about setting (Explorer -> Tools -> Folder Options ->
View tab -> check
"Launch folder windows in a a separate process") This was the approach
I was going to take to try this out but so far havent had the time to
get there.

Bret Pettichord and Brian Marrick teach several classes about web
testing, especially using ruby and the IEController. I beleive the
material is available for download.

Paul



James Britt said:
Hal said:
UI is often hard, especially GUI.

But I ran into an idea the other day that I was unfamiliar with.

'Expect' is sometimes used to wrap a GUI around a text interface --
spawn the process and interact with it.

Of course, that in itself is non-trivial perhaps.

But a text interface is easier to test, especially with Expect.

I've been using Ruby at my current job to test a web app via Internet
Explorer with the clIEController [0].

The downside is it only scripts IE, and (to the best of my knowledge),
you cannot get to the raw markup returned to the scripted browser, so it
doesn't help you verify, for example, that each page is rendered as you
expect.

I've hacked some code for the controller that walks the IE HTML node
tree and emits proper XML (not actual XHTML, though) so that one can run
xpath calls, via REXML, against the markup to see that certain data is
on the page, or check that a page fetch didn't return an error page.

If nothing else, it saves *much* time clicking through pages, filling in
forms, clicking submit, etc. My test script will spawn a score of
threads; browsers pop up all over the place, and I test a dozen
pages/site paths at a time.


James

[0] http://www.clabs.org/wtr/index.cgi?page=/ClIEController

Dunno if it's relavent, but perhaps a port of Perl's WWW::Mechanize
module is in order.

Regards,

Dan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top