Unit Testing in Rails Testing Database Actions Testing
Unit Testing in Rails Testing Database Actions
Testing the database n Rails projects typically involve manipulating a database n Testing with actual data, which you do not control, is infeasible n n n 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 n n 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 n YAML can be taken as an acronym for either n n 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 n n Rails uses YAML for configuring the database A typical entry in a. yml file would look like this: n n development: adapter: mysql database: cookbook host: localhost username: root password: This describes the development database n n Each line in the above defines a key-value pair The values are strings; they don’t need to be quoted
YAML example 2 n n Rails also uses YAML for describing the contents of databases An example might look like this: n 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 n The above example (adapted from http: //www. oreillynet. com/lpt/a/7086) describes hashes one and two of simple values: Strings, integers, dates n n Hashes can also be represented in brace notation: {name: John Smith, age: 33} Other types of data can also be represented
String literals n n String values do not need to be quoted Block literals (multiline strings) can be introduced with a pipe (“|”) or greater-than (“>”) symbol n The pipe preserves line ends and spacing: n n street: | 123 Tornado Alley Suite 16 The greater-than symbol allows text wrapping: n special. Delivery: > Follow the Yellow Brick Road to the Emerald City. Pay no attention to the man behind the curtain.
Lists n Lists are represented by single dashes (“-”): n movies: - Casablanca - North by Northwest - Notorious
Casting n n n YAML automatically detects simple types In the rare instances where casting is necessary, explicit casting can be performed with !! Examples: n n n n a: 123 b: "123" c: 123. 0 d: !!float 123 e: !!str 123 f: !!str Yes g: Yes h: Yes we have # an integer # a string, disambiguated by quotes # a float # also a float via explicit data type prefixed by (!!) # a string, disambiguated by explicit type # a string via explicit type # a boolean True No bananas # a string, "Yes" and "No" # dismabiguated by context
Creating a test database n You can begin by using SQL to create an (empty) test database: n n mysql -u root -p (When prompted for a password, just hit Enter) create database my. App_test; grant all on My. App_test. * to 'ODBC'@'localhost'; exit You can begin by giving commands to SQL to create a nonempty test database: n n mysql My. App_development <create. sql (create. sql is a file containing the appropriate commands)
Copying an existing database schema n You can copy the database schema (not the actual records) from the development database to the test database: n rake db: test: clone_structure
Creating a fixture file n n If you used scaffolding, you already have a file test/fixtures/table_name. yml for each table in your database schema Example: n n # Read about fixtures at http: //ar. rubyonrails. org/classes/Fixtures. html yamlin 5 minutes: id: 1 name: Yaml in 5 minutes url: http: //yaml. kwiki. org/? Yaml. In. Five. Minutes 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 n n If you used scaffolding, you already have a file test/unit/table_name. rb for each table in your database schema Example: n require File. dirname(__FILE__) + '/. . /test_helper' class Recipe. Test < Test: : Unit: : Test. Case fixtures : recipes # Replace this with your real tests. def test_truth assert true end n n n 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 n The idea of a unit test is that you call a method (or methods) in the model, then specify what the result should be n n n Your test methods must all begin with test_ and take no parameters n n n 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) n n 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: n fixtures : table_name
Assertions n n n n 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 n Here’s one way to create a record (in memory): n n Here’s an equivalent way: n n my_record = My. Class. create( : col_1 => value_1, : col_2 => value_2 ) Here’s how to delete a record: n n my_record. save Here’s how to create and save all in one go: n n my_record = My. Class. new( : col_1 => value_1, : col_2 => value_2 ) Here’s how to save the newly created record: n n my_record = My. Class. 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 n = My. Class. count
Useful methods I: Reading records n Here are some simple ways to get a record from the database: n my_record = My. Class. find(: first) n n my_records = My. Class. find(: all) n n Returns an array of all the records in the database my_record = My. Class. find(id) n n Returns the first record in the database Returns a record with the given id my_records = My. Class. find(id_1, id_2, . . . ) n Returns an array of records with the given ids
Useful methods II: Reading records n You won’t find these methods in the Rails API, because they are generated, based on the column names in your table n record = My. Class. find_by_col_1(value_1) n n record = My. Class. find_all_by_col_1(value_1) n n 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 = My. Class. find_by_col_1_and_col_2(value_1, value_2) n n 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 = My. Class. find_all_by_col_1_and_col_2(value_1, value_2) n 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 n Here’s how to update a record in the database: n n my_record = My. Class. 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 n n n 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 n def test_create_and_destroy initial_rec_count = Category. count new_rec = Category. new_rec. save assert_equal(initial_rec_count + 1, Category. count) new_rec. destroy assert_equal(initial_rec_count, Category. count) end
The End
- Slides: 20