200 likes | 403 Views
Unit Testing in Rails. Testing Database Actions. 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
E N D
Unit Testing in Rails Testing Database Actions
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 fromhttp://www.oreillynet.com/lpt/a/7086) describes hashesone 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.ymlfor each table in your database schema • Example: • # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.htmlyamlin5minutes: id: 1 name: Yaml in 5 minutes url: http://yaml.kwiki.org/?YamlInFiveMinuteswikipedia: 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.rbfor 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 endend • 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 • 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 • 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) • 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): • my_record = MyClass.new;my_record.col_1 = value_1;my_record.col_2 = value_2; • Here’s an equivalent way: • my_record = MyClass.new( :col_1 => value_1, :col_2 => value_2 ) • Here’s how to save the newly created record: • my_record.save • Here’s how to create and save all in one go: • my_record = MyClass.create( :col_1 => value_1, :col_2 => value_2 ) • Here’s how to delete a record: • 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) • Returns the first record in the database • my_records = MyClass.find(:all) • Returns an array of all the records in the database • my_record = MyClass.find(id) • 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) • If col_1 is the name of a column in the database, this returns a record whose value in that column is 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 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_17my_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