[ANN] testy.rb - ruby testing that's mad at the world

A

ara.t.howard

Or even less:

name = 42
assert{ name == ultimate.answer }


well, for one that won't run ;-) assert takes and arg and not a
block... but that aside

cfp:~ > cat a.rb
require 'test/unit'

class TC < Test::Unit::TestCase
def test_with_shitty_error_reporting
name = 42
assert name == 'forty-two'
end
end




cfp:~ > ruby a.rb
Loaded suite a
Started
F
Finished in 0.007649 seconds.

1) Failure:
test_with_shitty_error_reporting(TC) [a.rb:6]:
<false> is not true.

1 tests, 1 assertions, 1 failures, 0 errors


the error message '<false> is not true', on 8th line of output (out of
possibly thousands) makes me want to hunt the programmer down that
wrote that club him to death with a 2x4. the assert api facilities
insanity for the code maintainer

now, in testy

cfp:~/src/git/testy > ruby -I lib a.rb
---
my lib:
name compare:
failure:
expect:
name: forty-two
actual:
name: 42


my thinking, currently, is that the test writer should be forced to

. name the test suite
. name the test
. name the check

because doing these three things allows for informative error
reporting. testing just shouldn't facilitate obfusicating what went
wrong - imho.

cheers.

a @ http://codeforpeople.com/
 
S

Sean O'Halpin

well, for one that won't run ;-) assert takes and arg and not a block... = but
that aside

Phlip is referring to his own assert2, not the Test::Unit one. You
should check it out - it's really a great idea.
 
P

Phlip

ara.t.howard said:
report it in your ci tool

Don't integrate a broken build!

Are you implying that an incremental build server, such as CruiseControl, should
have enough errors it needs to count and report them?
 
P

Phlip

Sean said:
Phlip is referring to his own assert2, not the Test::Unit one. You
should check it out - it's really a great idea.

Tx, but it sucks! Here's the ideal test case (regardless of its .should or it{}
syntax:

test_activate
x = assemble()
g = x.activate()
g == 42
end

The test should simply reflect the variables and values of everything after the
activate line. Anything less is overhead. I _only_ want to spend my time setting
up situations and equating their results. No DSLs or reflection or anything!

Now, why can't our language just /do/ that for us? It knows everything it needs...
 
J

Jeremy Hinegardner

if we accept the research and assume that bugs scale linerarly with the #
of lines of code this is not good for robustness. this is one of my main
gripes with current ruby testing - my current rails app has about 1000
lines of code and 25,000 lines of testing framework!

This, of course, just means you are not writing big enough rails apps :).
 
A

ara.t.howard

Don't integrate a broken build!

Are you implying that an incremental build server, such as
CruiseControl, should have enough errors it needs to count and
report them?



i'm saying that whatever you are doing with automated tests, be it ci
or simply editor support, it's vastly easier for people to build tools
if the output is parsed in one line of YAML.load - that's all.

cheers.

a @ http://codeforpeople.com/
 
B

Bill Kelly

From: "ara.t.howard said:
dude - that is *hardcore* ;-)

I think some examples might help. These are from Phlip's
January 26, 2008 post Re: assert_{ 2.0 } on the XP list:


assert_{ 'a topic' == topics['first'] } output:

"a topic" == ( topics["first"] ) --> false
topics --> {"first"=>"wrong topic"}
( topics["first"] ) --> "wrong topic".



assert_{ [false] == shrew.accessories.map(&:active).uniq } output:

[true] == shrew.accessories.map(&:active).uniq --> false
shrew --> User 4
shrew.accessories --> [Accessory 501]
shrew.accessories.map(&:active) --> [false]
shrew.accessories.map(&:active).uniq --> [false].


It's badass.

Phlip's work on assert_{ 2.0 } immediately left me with two
impressons:

1. Having seen this, why would we ever accept anything less
awesome from any testing rig? :)

2. The facilities needed to implement this kind of reflection
should be part of ruby core.

....right? :)


Regards,

Bill
 
A

ara.t.howard

It's badass.

Phlip's work on assert_{ 2.0 } immediately left me with two
impressons:

1. Having seen this, why would we ever accept anything less
awesome from any testing rig? :)

2. The facilities needed to implement this kind of reflection
should be part of ruby core.

....right? :)

yes. it addresses one of my biggest issues with the current
standards: too many assertion methods and crappy reporting.

a @ http://codeforpeople.com/
 
A

ara.t.howard

I incredibly don't understand that. Our apps are big by Rails
standards, yet our test:code ratio is only 2.5:1. That's mostly
because we can't "refactor" the tests too much, or they are hard to
read.


i'm not talking about code to test ratios, but code to test
*framework* ratios. of course these numbers are a little bit high
(whitespace, comments, etc) but




cfp:/opt/local/lib/ruby/gems/1.8/gems > for gem in thoughtbot-* rspec*
faker* mocha*;do echo $gem;find $gem/lib -type f|grep .rb|xargs -n1
cat|wc -l;done
thoughtbot-factory_girl-1.2.0
937
thoughtbot-shoulda-2.9.1
3854
rspec-1.1.12
8773
rspec-1.1.3
7785
rspec-1.1.4
8083
faker-0.3.1
299
mocha-0.9.5
3294


most people would consider an 8000 line rails app 'large'.

a @ http://codeforpeople.com/
 
B

Bill Kelly

From: "Bill Kelly said:
assert_{ [false] == shrew.accessories.map(&:active).uniq } output:

[true] == shrew.accessories.map(&:active).uniq --> false
shrew --> User 4
shrew.accessories --> [Accessory 501]
shrew.accessories.map(&:active) --> [false]
shrew.accessories.map(&:active).uniq --> [false].

Er, sorry, I think I failed at reconstructing that from
the original email.

Presumably should be:

assert_{ [true] == shrew.accessories.map(&:active).uniq } output:

[true] == shrew.accessories.map(&:active).uniq --> false
shrew --> User 4
shrew.accessories --> [Accessory 501]
shrew.accessories.map(&:active) --> [false]
shrew.accessories.map(&:active).uniq --> [false].


Regards,

Bill
 
P

Phlip

ara.t.howard said:
thoughtbot-factory_girl-1.2.0
937
thoughtbot-shoulda-2.9.1
3854
rspec-1.1.12
8773

Well, the good news is Ruby is essentially a "post-Agile" environment. Your boss
can no longer fire you for writing "too many" unit tests. Many environments,
even in this day and age, have not matured so far!

The other good news is our enthusiasm has made us forget many of the original
Agile ground rules. TDD requires rapid feedback over tiny code changes, close to
the tested code, and setting up a Cucumber suite with a handful of mock objects
does not qualify as rapid feedback, running close to the code. Mocks are indeed
a way to help your code's design couple together. So BigAgileUpFront,
Ruby-style, might indeed be a big risk here.

If it isn't, we have to learn from it, because Ruby's BDD community represents
an amazing sight. The original BDD, FIT and Fitnesse, required hordes of
overpaid consultants to install, typically at big-iron sites. Ruby BDD, by
contrast, is just as light, free, and community-supported as Rails itself, and I
suspect it's the leading edge of "customer testing" research in general.
 
P

Phlip

You gotta use Ripper to convert the block to opcodes, then eval them
Who was just fussing about "too many lines of code in the framework"?
2. The facilities needed to implement this kind of reflection
should be part of ruby core.

Right:

report = journal do
my_code()
end

Inside journal{}, everything the Ruby VM does gets packed into a report. Then
you rip the report to get back to your details. That's what assert{ 2.0 }
_can't_ do, so it goes in two passes.

The first pass is a normal block.call, to detect success or failure. This is so
any bugs in the second pass don't throw the test run.

The second pass puts the block into Ripper to decompile it, and then the fun starts.
 
B

Brian Candler

Ara said:
you can basically do that too, but i continually forget which is
expected and which is actual and, as you know, that's a slippery error
to track down at times.

Perhaps - but it's one rule that only needs to be learned once.

I notice that testy supports check <name>, <expected>, <actual> too.

Testy does (intentially) force you to name your tests, whereas
Test::Unit will happily let you write

check <expected>, <actual>

I really don't like having to name each assertion, maybe because I'm
lazy or maybe because it feels like DRY violation. I've already said
what I want to compare, why say it again?
because littering the example code with esoteric testing framework
voodoo turns it into code in the testing language that does not
resemble how people might actually use the code

I agree with this. This is why I absolutely prefer Test::Unit (and
Shoulda on top of that) over Rspec.
i always end up writing
both samples and tests - one of the goals of testy is that, by having
a really simple interface and really simple human friendly output we
can just write examples that double as tests.

Hmm, this is probably an argument *for* having a DSL for assertions - to
make the assertions read as much like example code ("after running this
example, you should see that A == B and C < D")

Neither

result.check "bar attribute", :expected => 123, :actual => res.bar

nor

assert_equal 123, res.bar, "bar attribute"

reads particularly well here, I think. Ideally it should be as simple as
possible to write these statements of expectation. How about some eval
magic?

expect[
"res.foo == 456",
"res.bar == 123",
"res.baz =~ /wibble/"
]

Maybe need to pass a binding here, but you get the idea. (Before someone
else points it out, this is clearly a case which LISP would be very well
suited to handling - the same code to execute can also be displayed in
the results)

The problem here is reporting on expected versus actual, but perhaps you
could split on space and report the value of the first item.

expected:
- res.foo == 456
- res.bar == 123
unexpected:
-
test: res.baz =~ /wibble/
term: res.baz
value: "unexpected result"

Going too far this way down this path ends up with rspec, I think.

In fact, I don't really have a problem with writing

res.foo.should == 456

The trouble is the hundreds of arcane variations on this.

You solve this problem by only having a single test (Result#check), and
indeed if rspec only had a single method (should_equal) that would be
fairly clean too. However this is going to lead to awkwardness when you
want to test for something other than equality: e.g.

res = (foo =~ /error/) ? true : false
result.check "foo should contain 'error'", :expected=>true,
:actual=>res

Apart from being hard to write and read, that also doesn't show you the
actual value of 'foo' when the test fails.

Is it worth passing the comparison method?

result.check "foo should contain 'error'", foo, :=~, /error/

But again this is getting away from real ruby for the assertions, in
which case it isn't much better than

assert_match /error/, foo, "foo should contain 'error'"

assert_match /error/, foo # lazy/DRY version
get a listing of which tests/examples i can run

Yes, parseable results and test management are extremely beneficial.
Those could be retro-fitted to Test::Unit though (or whatever its
replacement in ruby 1.9 is called)

Getting rid of the at_exit magic is also worth doing.
you can also do something like this (experimental) to just make a
simple example

cfp:~/src/git/testy > cat a.rb
require 'testy'

Testy.testing 'my lib' do

test 'just an example of summing an array using inject' do
a = 1,2
a.push 3
sum = a.inject(0){|n,i| n += i}
end

end

Nice, could perhaps show the (expected) result inline too?

test 'an example of summing and array using inject' do
a = 1,2
a.push 3
sum = a.inject(0){|n,i| n += i}
end.<< 6

A bit magical though. Also, we can only test the result of the entire
block, whereas a more complex example will want to create multiple
values and test them all.
so the goal is making it even easier to have a user play with your
tests/examples to see how they work, and even to allow simple examples
to be integrated with your test suite so you make sure you samples
still run without error too. of course you can do this with test/unit
or rspec but the output isnt' friendly in the least - not from the
perspective of a user trying to learn a library, nor is it useful to
computers because it cannot be parsed - basically it's just vomiting
stats and backtraces to the console that are hard for people to read
and hard for computers to read. surely i am not the only one that
sometimes resorts to factoring out a failing test in a separate
program because test/unit and rspec output is too messy to play nice
with instrumenting code?

I agree. Part of the problem is that when one thing is wrong making 20
tests fail, all with their respective backtraces, it can be very hard to
see the wood for the trees. What would be nice would be a folding-type
display with perhaps one line for each failed assertion, and a [+] you
can click on to get the detail for that particular one.
yeah that's on deck for sure. i *do* really like contexts with
shoulda. but still


cfp:/opt/local/lib/ruby/gems/1.8/gems/thoughtbot-shoulda-2.9.1 > find
lib/ -type f|xargs -n1 cat|wc -l
3910


if we accept the research and assume that bugs scale linerarly with
the # of lines of code this is not good for robustness.

I disagree there - not with the research, but the implied conclusion
that you should never use a large codebase. Shoulda works well, and I've
not once found a bizarre behaviour in the testing framework itself that
I've had to debug, so I trust it.

(This is not true of other frameworks though. e.g. I spent a while
tracking this one down:
https://rspec.lighthouseapp.com/pro...677-rspec-doesnt-play-well-with-delegateclass)
this is one
of my main gripes with current ruby testing - my current rails app has
about 1000 lines of code and 25,000 lines of testing framework!

Yeah, but how many lines of Rails framework? :)

Cheers,

Brian.
 
B

Brian Candler

Ara said:
i always end up writing both samples and tests

Just a thought: I've found myself doing that too, but in particular I've
ran an example externally to be sure it works, then ended up pasting it
into the rdoc. So it would be really cool if rdoc could integrate the
examples directly in the appropriate place(s). I want to be sure that my
examples actually run as advertised, and at the moment I risk them
becoming out of date, so I'm definitely with you on that point.

However, one problem with having runnable examples/specs is that you
often have a lot of setup (and/or mock) code to make something run. This
risks adding too much noise to the examples, so structuring the code so
as to be able to filter that out would be a bonus - i.e. show me just
the example, not the setup or post-conditions. Not easy to do without
ripping the Ruby though.

I find the other kill-yourself part of testing is behavioural testing
with mock objects, but that's out of scope here I think :)

Regards,

Brian.
 
B

Bil Kleb

cfp:/opt/local/lib/ruby/gems/1.8/gems > for gem in thoughtbot-* rspec*  
faker* mocha*;do echo $gem;find $gem/lib -type f|grep .rb|xargs -n1  
cat|wc -l;done

Can you please rerun that with flog, flay, or reek in place of find
+xargs+cat+wc?

Thanks,
 

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

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,013
Latest member
KatriceSwa

Latest Threads

Top