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.
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.”
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.
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 |