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

ara.t.howard wrote:

···

On Mar 29, 2009, at 1:29 PM, Phlip wrote:

What, besides instantly fix it (or revert) do you want to do with an error message from a broken test?

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?

ara.t.howard wrote:

···

On Mar 29, 2009, at 5:01 PM, Sean O'Halpin wrote:

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

ah - reports via the binding i assume - that *is* a nice idea.

If only the binding were enough...

You gotta use Ripper to convert the block to opcodes, then eval them one by one.

ara.t.howard wrote:

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.

···

--
   Phlip

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

Thanks,

···

On Mar 29, 8:42 pm, "ara.t.howard" <ara.t.how...@gmail.com> wrote:

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

--
http://twitter.com/bil_kleb

Brian Candler wrote:

Bil Kleb wrote:

Sounds like you're pining for Python's doctest?

  doctest — Test interactive Python examples — Python 3.12.1 documentation

Ah, now that's a really interesting way of thinking about
examples/testing: in the form of an irb session. You can write your
tests just by mucking about in irb, and when it makes sense, just paste
the output somewhere.

  > foo = generate_foo
  => #<Foo:0xb7cd041c @attr1="hello", @attr2="world">
  > foo.attr1
  => "hello"
  > foo.attr2
  => "world"

This has been done : I found it quite some time ago on the Web and
polished it a bit :

#!/usr/bin/env ruby

# ---------------------------------------------------

# Copyright 2007 Clinton Forbes
# Modified Michel Demazure 11/07

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/\.

# ---------------------------------------------------

# Put this in the script directory of your Rails app

# this script is inspired by the doctest feature in Python:
# (http://docs.python.org/lib/module-doctest.html\)
# :although I never did get around to reading the original Python code.

···

#
# Feel free to improve upon this script, on the condition that you post
# a comment on
http://clintonforbes.blogspot.com/2007/08/doctest-for-ruby-and-rails.html
# and let me know about it so I can use it as well.
#
# to use, just put some doctests in your code and run this tool using
#
# script/doctest

# doctests look like this
=begin
#doctest Check that 1 + 1 = 2

x = 1

=> 1

x + x

=> 3

2 + 3

=> 6
=end

# or like this
#=begin
##doctest Test creating a Member
#>> m = Member.new()
#=> #nil, "name"=>nil, "hashed_password"=>nil, "admin"=>false,
"reset_key"=>nil, "newsletter_level"=>nil, "created_at"=>nil,
"email"=>nil}>
#>> m.name = "Clinton"
#=> "Clinton"
#>> m.admin = true
#=end

# or like this
=begin
#doctest Check that 1 + 2 = 3
irb(main):001:0> 1 + 2
=> 3
irb(main):002:0> 2 + 3
=> 5
=end

#get all of our Rails stuff
#require File.dirname(__FILE__) + '/../config/boot'
#require File.dirname(__FILE__) + '/../config/environment'

#build array of .rb files
#only looks in ./app directory by default. you can change this if you
keep
#extra code in other places (eg. ./vendor/plugins)

  # CODE_REGEX caters for standard IRB prompt and Rails script/console
  # prompt.
  CODE_REGEX = Regexp.new(/(>>|irb.*?>) (.*)/)
  RESULT_REGEX = Regexp.new(/=> (.*)/)

def get_ruby_files(dir_name)
  ruby_file_names =

  Dir.foreach(dir_name) do |file_name|
    unless file_name == '.' || file_name == '..'
      full_name = File.join(dir_name, file_name)
      if /.*\.rb$/ =~ full_name
        ruby_file_names << full_name
      elsif File.directory? full_name
        sub_files = get_ruby_files(full_name)
        ruby_file_names.concat(sub_files) unless sub_files.empty?
      end
    end
  end

  ruby_file_names
end

# When running tests, addresses of objects are never likely
# to be the same, so we wipe them out so tests don't fail
#
# for example: #
def normalize_result(input)
  input.gsub(/:0x([a-f0-9]){8}/, ':0xXXXXXXXX')
end

def failure_report(statement, expected_result, result)
  report = "\n FAILED" #add line number logic here
  report << " Code: " << statement << "\n"
  report << " Expected: " << expected_result << "\n"
  report << " But got: " << result
end

def run_doc_tests(doc_test)
  execution_context = binding()
  statement, report = '', ''
  wrong, passed = 0, 0
  doc_test.split("\n").each do |line|
    case line
    when CODE_REGEX
      statement << CODE_REGEX.match(line)[2]
    when RESULT_REGEX
      expected_result = normalize_result(RESULT_REGEX.match(line)[1])
      result = normalize_result(eval(statement,
execution_context).inspect)
      unless result == expected_result
        report << failure_report(statement, expected_result, result)
        wrong += 1
      else
        passed += 1
      end
      statement = ''
    end
  end
  return passed, wrong, report
end

def process_ruby_file(file_name)
  tests, succeeded, failed = 0, 0, 0
  file_report = ''
  code = File.read(file_name)
  code.scan(/=begin\s#doctest ([^\n]*)\n(.*?)=end/m) do |doc_test|
    file_report << "\n Testing '#{doc_test[0]}'..."
    passed, wrong, report = run_doc_tests(doc_test[1])
    file_report += (wrong == 0 ? "OK" : report)
    tests += 1
    succeeded += passed
    failed += wrong
  end
  file_report = "Processing '#{file_name}'" + file_report unless
file_report.empty?
  return tests, succeeded, failed, file_report
end

ruby_file_names = get_ruby_files(File.dirname(__FILE__))

total_report = "Looking for doctests in #{ruby_file_names.length}
files\n"
total_files, total_tests, total_succeeded, total_failed = 0, 0, 0, 0
ruby_file_names.each do |ruby_file_name|
  tests, succeeded, failed, report = process_ruby_file(ruby_file_name)
  total_files += 1 if tests > 0
  total_tests += tests
  total_succeeded += succeeded
  total_failed += failed
  total_report << report << "\n" unless report.empty?
end
total_report << "Total files: #{total_files}, total tests:
#{total_tests}, assertions succeeded: #{total_succeeded}, assertions
failed: #{total_failed}"
puts total_report
--
Posted via http://www.ruby-forum.com/\.

Ara Howard wrote:

I disagree. I 'learn' it each time I look at it, and then I forget it.
I think possibly because I think it's backwards.

The same goes for alias_method for me. I cannot tell you how many
times I've had to look up the order of old_name/new_name. And with
this, it's certainly because I think the values are backwards. (I just
had to look up the order to be sure.)

100% agree on both counts. i personally would always use the options
approach (:expected, :actual =>) because of that.

Ha, and I thought it was only me with alias_method and assert_equal
dyslexia. With alias_method, I find myself expecting it to act like
'cp' or 'ln', and I have to keep remembering it's the opposite of that,
even after a hundred times of re-remembering. And assert_equal order is
the opposite to the usual order in English, which I guess is a selling
point of rspec.

Along the same lines, I always ask irb about methods(true) vs
methods(false). I'm like, OK I just want what's recently defined --
true? Doh. OK then, false, I only want the recent definitions. False
-- do not include super. See? I am even forgetting, right now, as I
type this, which is which.

  "I curse you in ways multifarious!"
  "OK, will that be Curses.methods(true) or Curses.methods(false)?"
  "ARGGH!"

···

On Mar 30, 2009, at 12:20 PM, Phrogz wrote:

--
Posted via http://www.ruby-forum.com/\.

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/

···

On Mar 29, 2009, at 5:14 PM, Phlip wrote:

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?

--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

dude - that is *hardcore* :wink:

a @ http://codeforpeople.com/

···

On Mar 29, 2009, at 6:04 PM, Phlip wrote:

If only the binding were enough...

You gotta use Ripper to convert the block to opcodes, then eval them one by one.

--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

If only the binding were enough...

You gotta use Ripper to convert the block to opcodes, then eval them one by one.

dude - that is *hardcore* :wink:

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? :slight_smile:

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

....right? :slight_smile:

Regards,

Bill

···

From: "ara.t.howard" <ara.t.howard@gmail.com>

On Mar 29, 2009, at 6:04 PM, Phlip wrote:

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

a @ http://codeforpeople.com/

···

On Mar 29, 2009, at 6:30 PM, Bill Kelly wrote:

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? :slight_smile:

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

....right? :slight_smile:

--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

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

···

From: "Bill Kelly" <billk@cts.com>

You gotta use Ripper to convert the block to opcodes, then eval them one by one.

dude - that is *hardcore* :wink:

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.

i meant hardcore in a good way - really! :wink:

a @ http://codeforpeople.com/

···

On Mar 29, 2009, at 7:29 PM, Phlip wrote:

Who was just fussing about "too many lines of code in the framework"?

--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

Phlip wrote:

You gotta use Ripper to convert the block to opcodes, then eval them one by one.

dude - that is *hardcore* :wink:

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.

So you have to be careful in the block to avoid side effects, right? Otherwise pass1 and pass2 might start with different state.

···

--
       vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Joel VanderWerf wrote:

So you have to be careful in the block to avoid side effects, right? Otherwise pass1 and pass2 might start with different state.

No, I just document: Don't put anything in the block with a side-effect. The money-line (the line that calls your production code to test it), for example, should never be inside the assert{}.

The first pass is just a raw block.call, so the assertion must always get its actual status right...