Is it idiomatic Ruby to add an assert( ) method to Ruby's Kernel class?
No it's not a best practice. The best analogy to assert() in Ruby is just raising
raise "This is wrong" unless expr
and you can implement your own exceptions if you want to provide for more specific exception handling
How to put asserts in Selenium webdriver Ruby script?
Assertions, eg the .should==
, are typically used within a test framework such as RSpec, MiniTest, Cucumber, etc. These test frameworks are designed to have reporting of the assertion's pass or fail result.
Given that you are not using a test framework, you will need to manually handle the output of results.
The most straightforward way would be to drop the assertion part and use an if
statement:
element_value = browser.find_element(:xpath,"//*[@id="total"]/tbody/tr/td[4]/span").text
if element_value == "$466,634,599.67"
puts 'Test passed'
else
puts 'Test failed'
end
Note that the puts
statements are used to output results to the console (unless you have changed the default output location).
Alternatively, if you do want to use the assertion, you could do:
element_value = browser.find_element(:xpath,"//*[@id="total"]/tbody/tr/td[4]/span").text
element_value.should == "$466,634,599.67"
puts 'Test passed'
In this approach, the assertion will raise an exception if the test fails. The assertion would stop the execution of the code and would therefore not output the 'Test passed' message (as expected). If the test passes, the 'Test passed' message would get outputted.
Using asserts in Ruby production... yes or no?
Asserts really shouldn't be used in production code for two reasons.
assert x
is very functional, and as such hard to read. Using a raise/if combo adds readability.assert doesn't make it clear what error will be raised if the condition fails. While,
raise ObscureButInformitiveError if condition
lets the application layers further up do something relveant. Such as emailing an admin, or writing to a perticular log.
Ruby test/unit , how do I display the messages in asserts
There isn't much choice for TAP output specifically. One test framework that did was Bacon, which was an RSpec clone with TAP output, but it doesn't appear to be active anymore.
If what you want is compatibility with Continuous Integration, The ci_reporter gem can be used to convert test results (from both Test::Unit and RSpec) to the JUnit XML format which will make it work with Jenkins/Hudson, Bamboo, and other JUnit-aware CI servers.
But unlike RSpec, Test/Unit has a limitation where it doesn't report the tests that pass. There's a discussion about running Test/Unit through the RSpec HTML formatter which will include the names of the passing tests here.
2015 update: You may want to look into MiniTest, which is gaining popularity now and has many formatter plugins and is otherwise very flexible/extendable.
Making assertions from non-test-case classes
enum_cases
must be kept up to date when the production logic changes violating the DRY principle. This makes it more likely for there to be a mistake. Furthermore it is test code living in production, another red flag.
We can solve this by refactoring the case into a Hash lookup making it data driven. And also giving it a name describing what it's associated with and what it does, these are "handlers". I've also turned it into a method call making it easier to access and which will bear fruit later.
def foo_handlers
{
a: 1,
b: 2,
c: 3
}.freeze
end
def foo(input)
foo_handlers.fetch(input)
rescue KeyError
raise NotImplementedError, "Unhandled new case: #{input}"
end
Hash#fetch
is used to raise a KeyError
if the input is not found.
Then we can write a data driven test by looping through, not foo_handlers
, but a seemingly redundant expected
Hash defined in the tests.
class FooHelperTests < ActionView::TestCase
test "foo handles all expected inputs" do
expected = {
a: 1,
b: 2,
c: 3
}.freeze
# Verify expect has all the cases.
assert_equal expect.keys.sort, foo_handlers.keys.sort
# Drive the test with the expected results, not with the production data.
expected.keys do |key|
# Again, using `fetch` to get a clear KeyError rather than nil.
assert_equal foo(key), expected.fetch(value)
end
end
# Simplify the tests by separating happy path from error path.
test "foo raises NotImplementedError if the input is not handled" do
assert_raises NotImplementedError do
# Use something that obviously does not exist to future proof the test.
foo(:does_not_exist)
end
end
end
The redundancy between expected
and foo_handlers
is by design. You still need to change the pairs in both places, there's no way around that, but now you'll always get a failure when foo_handlers
changes but the tests do not.
- When a new key/value pair is added to
foo_handlers
the test will fail. - If a key is missing from
expected
the test will fail. - If someone accidentally wipes out
foo_handlers
the test will fail. - If the values in
foo_handlers
are wrong, the test will fail. - If the logic of
foo
is broken, the test will fail.
Initially you're just going to copy foo_handlers
into expected
. After that it becomes a regression test testing that the code still works even after refactoring. Future changes will incrementally change foo_handlers
and expected
.
But wait, there's more! Code which is hard to test is probably hard to use. Conversely, code which is easy to test is easy to use. With a few more tweaks we can use this data-driven approach to make production code more flexible.
If we make foo_handlers
an accessor with a default that comes from a method, not a constant, now we can change how foo
behaves for individual objects. This may or may not be desirable for your particular implementation, but its in your toolbox.
class Thing
attr_accessor :foo_handlers
# This can use a constant, as long as the method call is canonical.
def default_foo_handlers
{
a: 1,
b: 2,
c: 3
}.freeze
end
def initialize
@foo_handlers = default_foo_handlers
end
def foo(input)
foo_handlers.fetch(input)
rescue KeyError
raise NotImplementedError, "Unhandled new case: #{input}"
end
end
Now individual objects can define their own handlers or change the values.
thing = Thing.new
puts thing.foo(:a) # 1
puts thing.foo(:b) # 2
thing.foo_handlers = { a: 23 }
puts thing.foo(:a) # 23
puts thing.foo(:b) # NotImplementedError
And, more importantly, a subclass can change their handlers. Here we add to the handlers using Hash#merge
.
class Thing::More < Thing
def default_foo_handlers
super.merge(
d: 4,
e: 5
)
end
end
thing = Thing.new
more = Thing::More.new
puts more.foo(:d) # 4
puts thing.foo(:d) # NotImplementedError
If a key requires more than a simple value, use method names and call them with Object#public_send
. Those methods can then be unit tested.
def foo_handlers
{
a: :handle_a,
b: :handle_b,
c: :handle_c
}.freeze
end
def foo(input)
public_send(foo_handlers.fetch(input), input)
rescue KeyError
raise NotImplementedError, "Unhandled new case: #{input}"
end
def handle_a(input)
...
end
def handle_b(input)
...
end
def handle_c(input)
...
end
In ruby, can you execute assert_equal and other asserts while in irb?
Sure you can!
require 'test/unit'
extend Test::Unit::Assertions
assert_equal 5, 5 # <= nil
assert_equal 5, 6 # <= raises AssertionFailedError
What's going on is that all the assertions are methods in the Test::Unit::Assertions module. Extending that module from inside irb makes those methods available as class methods on main
, which allows you to call them directly from your irb prompt. (Really, calling extend SomeModule
in any context will put the methods in that module somewhere you can call them from the same context - main
just happens to be where you are by default.)
Also, since the assertions were designed to be run from within a TestCase
, the semantics might be a little different than expected: instead of returning true or false, it returns nil or raises an error.
Related Topics
How to Split String Across New Lines and Keep Blank Lines
How to Configure an Extra/Different Migrations Folder
What's the Difference Between Controllers and Actions in Ruby on Rails
Context Aware Authorization Using Cancan
How to Get Files Count in a Directory Using Ruby
How to Reference an Embedded Document in Mongoid
Declaring Instance Variables Iterating Over a Hash!
Carrierwave Crop Specific Version
How to Open a File and Search for a Word
Using Polymorphic Paths with Nested Associations
Rubymine: Rails Server Launcher Wasn't Found in the Project
How to Download a CSV File in Ruby on Rails
Ruby - Adding a Directory to $Load_Path - What Does It Do
How to Get the Width of Terminal Window in Ruby