Unit Testing in Rails

Download Report

Transcript Unit Testing in Rails

Unit Testing in Rails
Testing Database Actions
31-Mar-16
Testing the database

Rails projects typically involve manipulating a database

Testing with actual data, which you do not control, is infeasible






There are likely legal issues as well
You need to populate the test database with carefully designed data
Creating the database with SQL each time is inefficient
YAML provides a human-readable way of populating the test database
Rails unit tests can be set to re-initialize the database for each test
Some database tables might not be changed by testing




The tests may involve only reading the database
The database itself may be read-only
Re-initializing the database for each test is slow and inefficient
Rails unit tests can be set to not re-initialize the database for each test
YAML

YAML can be taken as an acronym for either




Yet Another Markup Language
YAML Ain’t Markup Language
The purpose of YAML is to represent typical data types
in human-readable notation
Structure is indicated by indentation with spaces; tabs
are not allowed
YAML example 1


Rails uses YAML for configuring the database
A typical entry in a .yml file would look like this:


development:
adapter: mysql
database: cookbook
host: localhost
username: root
password:
This describes the development database


Each line in the above defines a key-value pair
The values are strings; they don’t need to be quoted
YAML example 2


Rails also uses YAML for describing the contents of databases
An example might look like this:

one:
id: 1
category_id: 1
title: pizza
description: CB's favorite lunch
date: 2007-05-09
instructions: Phone pizza joint. Pay delivery guy. Chow down!
two:
id: 2
category_id: 2
title: iced tea
date: 2007-05-09

The above example (adapted from http://www.oreillynet.com/lpt/a/7086)
describes hashes one and two of simple values: Strings, integers, dates


Hashes can also be represented in brace notation: {name: John Smith, age: 33}
Other types of data can also be represented
String literals


String values do not need to be quoted
Block literals (multiline strings) can be introduced with
a pipe (“|”) or greater-than (“>”) symbol

The pipe preserves line ends and spacing:


street: |
123 Tornado Alley
Suite 16
The greater-than symbol allows text wrapping:

specialDelivery: >
Follow the Yellow Brick
Road to the Emerald City.
Pay no attention to the
man behind the curtain.
Lists

Lists are represented by single dashes (“-”):

movies:
- Casablanca
- North by Northwest
- Notorious
Casting



YAML automatically detects simple types
In the rare instances where casting is necessary, explicit casting
can be performed with !!
Examples:








a: 123
# an integer
b: "123"
# a string, disambiguated by quotes
c: 123.0
# a float
d: !!float 123 # also a float via explicit data type prefixed by (!!)
e: !!str 123
# a string, disambiguated by explicit type
f: !!str Yes
# a string via explicit type
g: Yes
# a boolean True
h: Yes we have No bananas # a string, "Yes" and "No"
# dismabiguated by context
Creating a test database

You can begin by using SQL to create an (empty) test
database:


mysql -u root -p
(When prompted for a password, just hit Enter)
create database myApp_test;
grant all on MyApp_test.* to 'ODBC'@'localhost';
exit
You can begin by giving commands to SQL to create a
nonempty test database:


mysql MyApp_development <create.sql
(create.sql is a file containing the appropriate commands)
Copying an existing database schema

You can copy the database schema (not the actual
records) from the development database to the test
database:

rake db:test:clone_structure
Creating a fixture file


If you used scaffolding, you already have a file
test/fixtures/table_name.yml
for each table in your database schema
Example:


# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
yamlin5minutes:
id: 1
name: Yaml in 5 minutes
url: http://yaml.kwiki.org/?YamlInFiveMinutes
wikipedia:
id: 2
name: Wikipedia article on YAML
url: http://en.wikipedia.org/wiki/YAML
If you don't have a YAML file already, you can create one in any
text editor
Creating test files


If you used scaffolding, you already have a file
test/unit/table_name.rb
for each table in your database schema
Example:

require File.dirname(__FILE__) + '/../test_helper'
class RecipeTest < Test::Unit::TestCase
fixtures :recipes
# Replace this with your real tests.
def test_truth
assert true
end
end



If you don't have a unit test file already, you can create one in a text editor
Note: The keyword __FILE__ stands for the file this code is in
test_helper is just a convenient place to put code used by many tests
Creating tests

The idea of a unit test is that you call a method (or methods) in
the model, then specify what the result should be



Your test methods must all begin with test_ and take no
parameters



Example: def test_total_cost_of_order; ...; end
If a method doesn’t begin with test_, it won’t be called automatically
You then test the result with one or more of the various assert
methods (see next slide)


In “ordinary” unit tests, you test the return value of a method
In database tests, you frequently have to access the database to get the
result
The unit test framework does all the rest of the work
If your test methods modify the database, so that it should be
reloaded after each test, use:

fixtures :table_name
Assertions
















assert(boolean, message=nil)
assert_block(message="assert_block failed") {|| ...}
assert_equal(expected, actual, message=nil)
assert_in_delta(expected_float, actual_float, delta, message="")
assert_instance_of(klass, object, message="")
assert_kind_of(klass, object, message="")
assert_match(regexp, string, message="")
assert_nil(object, message="")
assert_no_match(regexp, string, message="")
assert_not_equal(expected, actual, message=nil)
assert_not_nil(object, message="")
assert_not_same(expected, actual, message=nil)
assert_nothing_raised(*args) {|| ...}
assert_same(expected, actual, message="")
flunk(message="flunked")
Plus a few others....
Useful methods I: Creating and saving records

Here’s one way to create a record (in memory):


Here’s an equivalent way:


my_record = MyClass.create( :col_1 => value_1, :col_2 => value_2 )
Here’s how to delete a record:


my_record.save
Here’s how to create and save all in one go:


my_record = MyClass.new( :col_1 => value_1, :col_2 => value_2 )
Here’s how to save the newly created record:


my_record = MyClass.new;
my_record.col_1 = value_1;
my_record.col_2 = value_2;
my_record.destroy
Here’s how to find out how many records are in the database:

n = MyClass.count
Useful methods I: Reading records

Here are some simple ways to get a record from the database:

my_record = MyClass.find(:first)


my_records = MyClass.find(:all)


Returns an array of all the records in the database
my_record = MyClass.find(id)


Returns the first record in the database
Returns a record with the given id
my_records = MyClass.find(id_1, id_2, ...)

Returns an array of records with the given ids
Useful methods II: Reading records

You won’t find these methods in the Rails API, because they are generated,
based on the column names in your table

record = MyClass.find_by_col_1(value_1)


record = MyClass.find_all_by_col_1(value_1)


If col_1 is the name of a column in the database, this returns an array of records whose
value in that column is value_1.
record = MyClass.find_by_col_1_and_col_2(value_1, value_2)


If col_1 is the name of a column in the database, this returns a record whose value in
that column is value_1.
If col_1 and col_2 are the names of columns in the database, this returns a record whose
value in col_1 is value_1 and whose value in col_2 is value_2.
record = MyClass.find_all_by_col_1_and_col_2(value_1, value_2)

If col_1 and col_2 are the names of columns in the database, this returns an array of
records whose value in col_1 is value_1 and whose value in col_2 is value_2.
Useful methods III: Updating records

Here’s how to update a record in the database:


my_record = MyClass.find(id)
my_record.col_2 = value_17
my_record.save
The save method uses the id field of the record to
determine if the record already exists in the table



If the record exists, it is updated
If the record doesn’t exist, it is created
In fact, save just calls another method, create_or_update
Example test method

def test_create_and_destroy
initial_rec_count = Category.count
new_rec = Category.new
new_rec.save
assert_equal(initial_rec_count + 1, Category.count)
new_rec.destroy
assert_equal(initial_rec_count, Category.count)
end
The End