RubyFIT

Framework for Integrated Tests in Ruby

The examples in this document are taken from the FitBook by Rick Mugridge and Ward Cunningham.

Download the code bundle for this basic and the advanced tutorials.

Testing calculations with ColumnFixture tables

Imagine you want to express the following business rule, taken from page 13 of the FitBook, using a FIT table:

A 5 percent discount is provided whenever the total purchase is greater than $1,000.

This seems to be a test for some kind of calculation to be carried out correctly according to the stated rule: a typical context for the use of a ColumnFixture. A possible HTML table containing data to run the test against could be the following, as appears on page 14 of the FitBook:

CalculateDiscount
amount discount()
0.00 0.00
100.00 0.00
999.00 0.00
1000.00 0.00
1010.00 50.50
1100.00 55.00
1200.00 60.00
2000.00 100.00

The first row in the table carries the fixture’s name. In RubyFIT, that also is the name of the file containing the fixture, where the camel cased words are lied without capital letters and separated by underscores. Programmers can start creating a file named calculate_discount.rb and writing the skeleton of the CalculateDiscount fixture:

require 'fit/column_fixture'

class CalculateDiscount < Fit::ColumnFixture
end

The amount column holds the given values used as input for the test: it must be represented by a writable public member in the fixture class.

class CalculateDiscount < Fit::ColumnFixture
  attr_writer :amount
end

The discount() column holds the calculated results that are expected as output from the test. (The label for a calculated result column has () after the name.) Hence, it must be represented by a public method in the fixture class.

class CalculatedDiscount < Fit::ColumnFixture
  attr_writer :amount
  def discount
  end
end

Fixtures represent a software layer between tests data as HTML tables on a side and the application to test on the other side. The FIT framework provides a means to interact with tests data; so, inside the fixture a means must exist to interact within the application. A way to exercise the application under test can be provided by a private member to set up in the initialization method of the fixture.

class CalculatedDiscount < Fit::ColumnFixture
  attr_writer :amount
  def initialize
    @application = Discount.new
  end
  def discount
  end
end

Finally, the discount method in the fixture must contain the operations executed to check the test cases one at a time, using the amount field as the input data, offering actual results which will be verified on the basis of the expected results provided by the HTML table.

def discount
  @application.get_discount @amount
end

As an aside note, the business rule “a 5 percent discount is provided whenever the total purchase is greater than $1,000” can be represented by the following bare bones application:

class Discount
  def get_discount amount
    (amount >= 1000) ? (amount / 100.0) * 5 : 0.0
  end
end

Supposing the CalculateDiscount table is contained in a file called TestDiscount.html, the following command runs the test against RubyFIT and generates a report in a file called ReportDiscount.html:

fit TestDiscount.html ReportDiscount.html

The resulting table appears as follows, on page 15 of the FitBook:

CalculateDiscount
amount discount()
0.00 0.00
100.00 0.00
999.00 0.00
1000.00 0.00 expected
50.0 actual
1010.00 50.50
1100.00 55.00
1200.00 60.00
2000.00 100.00

The FitBook again well describes the results of the test: “The report contains a single failing test. This failed test is red because the system under test is incorrect when the amount is 1,000.00. The actual value that was calculated by the system under test (50.0) is added to the report.”

Testing processes with ActionFixture tables

Imagine you want to express the following business rule for changes to a chat server, taken from page 26 of the FitBook, using a FIT table:

A chat user connects, creates a new chat room, and enters it. Another user connects and enters the same room. Check that the room has two occupants.

This is a test for a sequence of actions made to a system to have the desired effects. This is the typical context for the use of an ActionFixture, which the FitBook describes as using “the metaphor of a device to control the test: values can be entered into input fields, buttons pressed, and output fields checked against expected values.” A possible HTML table containing data to run the test against could be the following, as appears on page 27 in the FitBook:

fit.ActionFixture
start ChatServerActions
enter user anna
press connect
enter room lotr
press new room
press enter room
enter user luke
press connect
press enter room
check occupant count 2

The first row in the table carries the fixture’s name: this is a table for an ActionFixture. The second row starts another fixture which the ActionFixture will perform its actions over, and which represents the interface towards the application under test. As happened for the ColumnFixture example, RubyFIT uses the name of the fixture as the name of the file containing it, where the camel cased words are lied without capital letters and separated by underscores. A possible skeleton for the ChatServerActions fixture in the chat_server_actions.rb file can be lied as follows:

require 'fit/fixture'

class ChatServerActions < Fit::Fixture
end

As for the other rows: the first column contains the name of the actions that the ActionFixture will perform on the ChatServerActions fixture; the second column contains the name of methods in ChatServerActions which will be activated in response to the actions. Some methods are input methods, such as the ones corresponding to enter and press actions; some input methods can take parameters, which will be contained in the third column of the table, such as the methods corresponding to enter actions. Some methods are used to verify expected results, which will be contained in the third column of the table as well, such as the methods corresponding to check actions.

The ChatServerActions skeleton can then be augmented as follows:

class ChatServerActions < Fit::Fixture
  def user user_name; end
  def connect; end
  def room room_name; end
  def new_room; end
  def enter_room; end
  def occupant_count; end
end

Finally, given an instance of the application under test in the initialization method of this fixture, the body of the methods can be filled in with appropiate stimulations towards the system.

class ChatServerActions < Fit::Fixture
  def initialize
    @chat = ChatRoom.new
  end
  def user user_name
    @user_name = user_name
  end
  def connect
    @chat.connect_user @user_name
  end
  def room room_name
    @room_name = room_name
  end
  def new_room
    @chat.user_creates_room @user_name, @room_name
  end
  def enter_room
    @chat.user_enters_room @user_name, @room_name
  end
  def occupant_count
    @chat.occupants @room_name
  end
end

Supposing that our table is contained in a file called TestChatServer.html, the following command runs the test against RubyFIT and generates a report in a file called ReportChatServer.html:

fit TestChatServer.html ReportChatServer.html

The resulting table appears as follows, on page 28 of the FitBook:

fit.ActionFixture
start ChatServerActions
enter user anna
press connect
enter room lotr
press new room
press enter room
enter user luke
press connect
press enter room
check occupant count 2

Only the results corresponding to check actions are marked: now in green, showing that the occupant count value was as expected.

Testing lists with RowFixture tables

Imagine you want to express the following business rule, taken from page 34 in the FitBook, using a FIT table:

The discount percentage that is provided depends on a customer’s projected future value (high, medium, low), how much is owned, and the total amount of any one purchase. As the specifics of discount groups will change from time to time, their defining values will be stored in a configuration file.

Complex rules such as this have often to be tested in many different steps. For this document’s purposes, we just want to follow section 5.2 in the FitBook, and control that the current discount groups are stored in the file following the expected order. This seems to be a test designed for querying the system, and verifying that the list resulting from the query contains the expected elements: as such, it falls in the context of the typical use of a RowFixture.

A possible HTML table containing data to run the test against could be the following, as appears on page 34 of the FitBook:

DiscountGroupOrderedList
order future value max owing min purchase discount percent
1 low 0.00 0.00 0
2 low 0.00 2000.00 3
3 medium 500.00 600.00 3
4 medium 0.00 500.00 5
5 high 2000.00 2000.00 10

The first row in the table contains the name of the fixture, to be mapped as usual onto the file name containing it, using the patterns already previously described. The file discount_group_ordered_list.rb will then contain a class DiscountGroupOrderedList with the following skeleton:

require 'fit/row_fixture'

class DiscountGroupOrderedList < Fit::RowFixture
end

Simple RowFixture classes typically feature two important methods: the first is called query and returns a list of objects to be checked against expected results in the HTML table; the second is called get_target_class and returns the class those objects belong to.

class DiscountGroupOrderedList < Fit::RowFixture
  def query; end
  def get_target_class
    OrderedDiscountGroup
  end
end

The OrderedDiscountGroup class of the objects to be compared can be easily constructed using table columns as a reference: the elements in the second row represent names of fields that will need to be publicly accessible for the comparison to be carried over.

class OrderedDiscountGroup
  attr_reader :order, :future_value
  attr_reader :max_owing, :min_purchase, :discount_percent
  def initialize order, future_value, max_owing, min_purchase, discount_percent
    @order = order
    @future_value = future_value
    @max_owing = max_owing
    @min_purchase = min_purchase
    @discount_percent = discount_percent
  end
end

Besides, due to RubyFIT internal comparison mechanisms, a target class for RowFixture classes needs to specify metadata describing the type of each field or method used in the HTML table to check for expected results. Metadata is represented under the form of a static hash, where keys are the names of fields or methods from the target class (method names ending in ()), and values are classes corresponding to the type of those fields or return type of those methods.

class OrderedDiscountGroup
  # ...
  @@metadata = { 'order' => Fixnum, 'future_value' => String,
                 'max_owing' => Float, 'min_purchase' => Float,
                 'discount_percent' => Float }
  def OrderedDiscountGroup.metadata; @@metadata; end
end

This class, however, belongs to the same interface layer between test tables and application under test as the fixture itself. In fact, the actual list of discount groups is taken from the system in the fixture’s query method; then these elements are mapped onto a list of OrderedDiscountGroup elements with an indication of their order added.

def query
  groups = DiscountGroup.get_elements
  ordered_groups = []
  groups.each_with_index { |e, i|
    group = OrderedDiscountGroup.new(i + 1, e.future_value,
                                     e.max_owing, e.min_purchase,
                                     e.discount_percent)
    ordered_groups << group
  }
  ordered_groups
end

Supposing that our table is contained in a file called TestDiscountGroup.html, the following command runs the test against RubyFIT and generates a report in a file called ReportDiscountGroup.html:

fit TestDiscountGroup.html ReportDiscountGroup.html

The resulting table appears as follows, on page 35 of the FitBook:

DiscountGroupOrderedList
order future value max owing min purchase discount percent
1 low 0.00 0.00 0
2 low 0.00 2000.00 3
3 medium 500.00 600.00 3
4 medium 0.00 500.00 5
5 high 2000.00 2000.00 10