Unit Testing in Ruby

quaggahooliganInternet και Εφαρμογές Web

5 Φεβ 2013 (πριν από 4 χρόνια και 4 μήνες)

87 εμφανίσεις

18
-
Mar
-
13

Unit Testing in Ruby

Programming methodologies


The grim facts:


The
majority

of large programming projects fail


Projects that succeed are usually full of bugs


Modifying and debugging programs usually introduces yet more bugs


Hence, it is hazardous to modify a “working” program


The time spent maintaining a program far exceeds (10x?) the amount of
time it took to write the program


Programming methodologies

are attempts to solve these problems
by properly organizing programs and/or programmers


The current (and best) methodologies are the “agile” methodologies


“Agile” means writing software that is easily changed


XP

(
Exteme Programming
) is the best known agile methodology


XP tends to work best for small groups of programmers


Some ideas from agile programming


There is no “silver bullet,” but agile methodologies are
the best we have at present


Most large programming projects fail, so...


“Write the simplest thing that can possibly work.”


Always have a working version, no matter how little it does


Never add a feature until all known bugs have been fixed


Any code that hasn’t been tested is assumed to be wrong


Have tests for
all

code, and keep it up to date


Run tests frequently

very

frequently


Tests must be trivially easy to run, or you won’t bother running them


Test suites


Obviously you have to test your code to get it working in the
first place


You can do
ad hoc

testing (running whatever tests occur to you at the
moment), or


You can build a
test suite

(a thorough set of tests that can be run at any
time)


Disadvantages of a test suite


It’s a lot of extra programming


This is true, but use of a good
test framework

can help quite a bit


You don’t have time to do all that extra work


False

Experiments repeatedly show that test suites reduce debugging time
more than the amount spent building the test suite


Advantages of a test suite


Reduces total number of bugs in delivered code


Makes code much more maintainable and refactorable


This is a
huge

win for programs that get actual use!

XP approach to testing


In the Extreme Programming approach,


Tests are written before the code itself


If code has no automated test case, it is
assumed not to work


A test framework is used so that automated testing can be done after
every small change to the code


This may be as often as every 5 or 10 minutes


If a bug is found after development, a test is created to keep the bug from
coming back


Consequences


Fewer bugs


More maintainable code


Continuous integration

During development, the program
always
works

it may not do everything required, but what it does, it does right

Terminology


A
test fixture

sets up the data (both objects and primitives) that
are needed to run tests


Example: If you are testing code that updates an employee record, you
need an employee record to test it on


A
unit test

is a test of a
single

class


A
test case

tests the response of a single method to a particular
set of inputs


A
test suite

is a collection of test cases


A
test runner

is software that runs tests and reports results



An
integration test

is a test of how well classes work together


JUnit provides some limited support for integration tests

test suite

Once more, in pictures


A
unit test

tests the methods in a
single class


A
test case

tests (insofar as
possible) a single method


You can have multiple test cases
for a single method


A
test suite

combines unit tests


The
test fixture

provides software
support for all this


The
test runner

runs unit tests or
an entire test suite


Integration testing

(testing that it
all works together) is not well
supported by JUnit

unit test (for one class)

another unit test

test case (for one method)

another test case

test case (for one method)

another unit test

another test case

another test case

another test case

test fixture

test runner

another test case

The test runner


The test runner runs all your tests


If they
all

succeed, you get a green bar


If
any

fail, you get a red bar

and links to the tests that failed


How
not

to write a unit test


Unit tests must be fast and easy to run

or you won’t run them


The
only

output you should need to look at, in order to see that all
tests passed, at is the green bar


Of course, if you get a red bar, you need to explore further


Don’t do any output from your unit tests!



Ideally, the methods you are testing should not do any output


In most well
-
written programs, there is a
separation of concerns

methods
either compute or do output, but not both


It is possible to write unit tests for methods that do output, but that is a
slightly advanced topic I won’t cover here

How to write a unit test class


A unit test class is a class
you

write that extends
Test::Unit::TestCase


You will need the line

require 'test/unit'


Your test class will inherit the following methods:


def setup


This a method that will be called
before

each of your test methods


Typically, you will
override

this method and use it to assign values to
some instance variables you need in testing


def teardown()


This a method that will be called
after

each of your test methods


Typically you will just ignore this method, unless you need to close files


You will also write any number of test methods, all of which
have the form
def test_
Something


Something

is usually, but not necessarily, the name of the method you
want to test


Inside each test method, you will do some computations and call one or
more
assert

methods to test the results

Available assertion methods


assert
boolean


assert_equal
expected
,
actual


Uses
==


assert_same
expected
,
actual


Uses
equal?


assert_not_equal
expected
,
actual


assert_not_same
expected
,
actual


assert nil
object


assert not_nil
object


assert_block
block


All these methods can take an additional
message
argument


This is not a complete listing of the assert methods


The first two methods are by far the most commonly used

Structure of a unit test


require "test/unit"

require "
file_to_be_tested
"


class CountTest < Test::Unit::TestCase



def setup


# Perform initializations here


end



def test_
some_method


# Tests go here


end



def teardown


# Release any resources (usually not needed)


end


end



Testing for exceptions


Methods
should

throw exceptions if they are called incorrectly


You can test whether a method throws an exception when it
ought to



def test_exceptions


begin



# Call the method that should throw an exception


rescue Exception
# or you can test for specific exceptions


return

# The exception happened, so the test passes


end


flunk

end


Ruby also has

assert_raise
and

assert_throws
methods, but I
haven’t been able to make them work

A complete example


class Counter


attr_reader :value





def initialize


@value = 0


end





def increment *n


if n == [ ]


@value += 1


else


@value += n[0]


end


end






def reset


@value = 0


end



end



The test class, part
1


require "test/unit"

require "counter"


class CountTest < Test::Unit::TestCase





def setup


@c = Counter.new


end





def test_increment_with_no_args


assert_equal 0, @c.value


@c.increment


assert_equal 1, @c.value


@c.increment


assert_equal 2, @c.value


end





def test_increment_with_arg


assert_equal
0
, @c.value


@c.increment
3


assert_equal
3
, @c.value


@c.increment
5


assert_equal
8
, @c.value


end





def test_reset


@c.increment


@c.increment
10


assert @c.value >
0


@c.reset


assert @c.value.zero?


end

The test class, part 2



def test_exceptions


begin


@c.increment 'four'


return


rescue Exception


end


flunk


end


end
# of the test class

Test suites


A test suite is a collection of unit tests


In Ruby, all you have to do is
require

each test file


Example suite (containing just the one unit test:


require 'ruby_tests'




Note: In RadRails, running a test suite produces only text output

Test
-
Driven Development (TDD)


It is difficult to add unit tests to an existing program


The program probably wasn’t written with testing in mind


It’s actually better to write the tests
before

writing the code you
want to test



This seems backward, but it really does work better:


When tests are written first, you have a clearer idea what to do when you
write the methods


Because the tests are written first, the methods are necessarily written to
be testable


Writing tests first encourages you to write simpler, single
-
purpose
methods


Because the methods will be called from more than one environment (the
“real” one, plus your test class), they tend to be more independent of the
environment


Recommended approach

1.
Write a test for some method you intend to write


If the method is fairly complex, test only the simplest case

2.
Write a stub for the method

3.
Run the test and make sure it fails

4.
Replace the stub with code


Write just enough code to pass the tests

5.
Run the test


If it fails, debug the method (or maybe debug the test); repeat until the
test passes

6.
If the method needs to do more, or handle more complex
situations,
add the tests for these first,

and go back to step
3

The End

If you don't unit test then you aren't a software engineer, you are a
typist who understands a programming language.


--
Moses Jones



1. Never underestimate the power of one little test.


2. There is no such thing as a dumb test.


3. Your tests can often find problems where you're not expecting them.


4. Test that everything you say happens actually does happen.


5. If it's worth documenting, it's worth testing.



--
Andy Lester