Skip to content

Vidalia uses layers to simplify the creation and maintenance of your automated API and database test suite.

Notifications You must be signed in to change notification settings

jrotter/vidalia

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vidalia

What is Vidalia?

Vidalia uses layers to simplify the creation and maintenance of API and database calls in your automated test suite.

A typical automated test suite can be broken down into layers:

  • A workflow layer on top, which dictates the user’s path through the application. Think in terms of verbs and user stories: “As a user, when I create a new message, I expect it to be sent to the desired recipient.” The workflow layer cares about user actions and results, not about the mechanics of entering or retrieving data.

  • A control layer beneath, which defines the specific interactions with the application. This control layer would be implemented using API calls or direct database calls and cares only about getting data into and out of the application.

Vidalia allows for easy separation of the workflow and control layers by sitting between them. At the control layer, it provides a simple infrastructure for creation and maintenance of API/database calls. At the workflow layer, it provides a set of basic test directives, allowing for easier manipulation of data without having to worry about the details of the underlying interfaces.

How does Vidalia work?

At the control layer, Vidalia allows you to define three basic artifacts: Interfaces, Objects, and Elements

Interface

An Interface is the top layer of the Vidalia artifact hierarchy. It is simply a collection of Objects.

Object

An Object is a collection of data elements that can be manipulated via the defined Interface. Basic Object operations (e.g. create, read, update, delete) or more complex ones can be defined by the user.

Object operations

Operations can be added for an Object to communicate via the Interface. In some cases, the operation will retrieve data via the interface. In others the operation will create or alter data via the interface.

Element

An Element is a single piece of data within an Object. All Elements have a set of directives associated with them to enable easier implementation of your test automation.

Element Directives

Manipulation and verification of each data Element is built around the two primary directives get and set:

  • get - reads and returns the current value of the Element from the Object. Usually performed after data is retreived via an Object operation.

  • set - updates the current value of the Element in preparation for the next operation on the Object.

Once these primary directives are defined, Vidalia puts them together to build the following directives for free:

  • retrieve - Reads and returns the current value from the Element. Does not log anything. (This is the user-callable version of get.)

  • update - Sets a new Element value. Logs the value of the Element both before and after the change for auditing purposes.

  • confirm - Tests the value of the Element, returning true if the input matches the value of the Element and false otherwise. Does not log anything.

  • verify = Tests the value of the Element, raising an exception if the input does not match the value of the Element. Returns true otherwise. Logs successes for auditing purposes.

How do I install Vidalia?

Install the Vidalia gem from the command line as follows:

$ gem install vidalia

How do I get started with Vidalia?

The code examples used below are all taken from the Vidalia repository in the examples/example1 directory. In this directory, we will be automating control of user data in the database defined with by the db_helper.rb script.

Remember, Vidalia breaks the code into two layers, so we’ll define those layers in different files. The workflow layer will be defined in demo.rb, but let’s start by using Vidalia to define the control layer in user_object.rb.

Define the Interface

Our first step is to define an Interface. If the interface has already been defined elsewhere, that definition will be returned. This allows multiple Object definitions (in multiple files) to be added to the same Interface.

app_db = Vidalia::Interface.define(:name => "Application DB")

Define our Object

Now we can define our object. Since we’re working with a user entry in our database, we’ll name it “User”. The definition will indicate that the Object’s parent is our Interface. Additionally, the Object definition will contain a code block to be run whenever this Object is instantiated. A typical Object definition block will instantiate object variables.

user_object = Vidalia::Object.define(:name => "User", :parent => app_db) {
  singleton_class.class_eval { attr_accessor :id }
  @id = nil
  singleton_class.class_eval { attr_accessor :first_name }
  @first_name = nil
  singleton_class.class_eval { attr_accessor :last_name }
  @last_name = nil
  singleton_class.class_eval { attr_accessor :username }
  @username = nil
}

Define Object operations

In our example, we will define operations to create, retrieve, update, and delete a User object. Note that the use of Objects and Elements will be different depending on the type of operation. To prepare for a “retrieve” operation, the user will set whatever elements are needed to perform the retrieval, after which the operation will be called. To perform an “create” operation, all impacted Elements must be set before the creation.

Let’s start with “create”:

user_object.add_method(:name => "create") {
  db = SQLite3::Database.open "users.db"
  sql_string = "INSERT INTO users VALUES("
  sql_string << @id.to_s
  sql_string << ","
  sql_string << @first_name.to_s
  sql_string << ","
  sql_string << @last_name.to_s
  sql_string << ","
  sql_string << @username.to_s
  sql_string << ")"
  Vidalia.log("Adding a user with DB call \"#{sql_string}\"")
  db.execute sql_string
  db.close
}

As described above, this operation expects all of the pertinent Elements to already be set. The operation itself just performs a single database operation to create the new Object.

Next we define a “read” operation:

user_object.add_method(:name => "read") { |inhash|
  db = SQLite3::Database.open "users.db"
  if inhash[:id] 
    sql_string = "SELECT * FROM users WHERE id = #{inhash[:id]};"
    Vidalia.log("Reading user ID=#{inhash[:id]} with DB call \"#{sql_string}\"")
  elsif inhash[:username]
    sql_string = "SELECT * FROM users WHERE username = #{inhash[:username]};"
    Vidalia.log("Reading user username=#{inhash[:username]} with DB call \"#{sql_string}\"")
  end
  results = db.execute sql_string

  # Handle empty array
  results << [nil,nil,nil,nil] if results.size == 0

  result = results[0]
  @id = result.shift
  @first_name = result.shift
  @last_name = result.shift
  @username = result.shift
  db.close
}

Note that this operation doesn’t require any Element data to be pre-set. It will overwrite whatever is stored in the Object with the data returned from the database call.

The “update” operation is similar to “create” in that Element data is already expected to be defined for this operation:

user_object.add_method(:name => "update") {
  db = SQLite3::Database.open "users.db"
  sql_string = "Update users SET first_name=\'"
  sql_string << @first_name.to_s
  sql_string << "\', last_name=\'"
  sql_string << @last_name.to_s
  sql_string << "\', username=\'"
  sql_string << @username.to_s
  sql_string << "\' WHERE id="
  sql_string << @id.to_s
  sql_string << ";"
  Vidalia.log("Updating user ID=#{@id} with DB call \"#{sql_string}\"")
  db.execute sql_string
  db.close
}

And “delete” requires just an Element or two to accomplish its goal:

user_object.add_method(:name => "delete") { |inhash|
  db = SQLite3::Database.open "users.db"
  if inhash[:id] 
    sql_string = "DELETE FROM users WHERE id = #{inhash[:id]};"
    Vidalia.log("Deleting user ID=#{inhash[:id]} with DB call \"#{sql_string}\"")
  elsif inhash[:username]
    sql_string = "DELETE * FROM users WHERE username = #{inhash[:username]};"
    Vidalia.log("Deleting user username=#{inhash[:username]} with DB call \"#{sql_string}\"")
  end
  db.execute sql_string
  db.close
}

Finally, we will complete the Object definition by telling Vidalia how to set and get each Element value. To do this, we have to define the Element objects:

id_element = Vidalia::Element.define(:name => "ID", :parent => user_object) 
id_element.add_get { |opts|
  @parent.id
}
id_element.add_set { |value|
  @parent.id = value
}

For the most part, Element definitions will be this simple. Just name them, associate them with their parent Object, and specify the “get” and “set” directives. With the get and set directives defined, Vidalia will use them to build update, retrieve, confirm, and verify directives for you. We’ll use those in the workflow layer in a bit.

We need three more Elements to complete our example:

first_name_element = Vidalia::Element.define(:name => "First Name", :parent => user_object) 
first_name_element.add_get { |opts|
  @parent.first_name
}
first_name_element.add_set { |value|
  @parent.first_name = value
}

last_name_element = Vidalia::Element.define(:name => "Last Name", :parent => user_object) 
last_name_element.add_get { |opts|
  @parent.last_name
}
last_name_element.add_set { |value|
  @parent.last_name = value
}

username_element = Vidalia::Element.define(:name => "Username", :parent => user_object) 
username_element.add_get { |opts|
  @parent.username
}
username_element.add_set { |value|
  @parent.username = value
}

And now our Object declaration is complete! Now on to the workflow layer.

Defining Interface, Objects, and Elements using the DSL

Alternatively, the Vidalia DSL can be used to define an interface, its objects, and each object’s elements. The intent of the DSL is to provide a cleaner way to define Vidalia artifacts by minimizing boilerplate code and eliminiating the need to store definitions in variables and pass them to descendants. Here is how the previous examples can be re-written using the DSL:

interface 'Application DB' do

  object 'User' do
    init do
      singleton_class.class_eval { attr_accessor :id  }
      @id = nil
      singleton_class.class_eval { attr_accessor :first_name  }
      @first_name = nil
      singleton_class.class_eval { attr_accessor :last_name  }
      @last_name = nil
      singleton_class.class_eval { attr_accessor :username  }
      @username = nil
    end

    create do
      singleton_class.class_eval { attr_accessor :id }
      @id = nil
      singleton_class.class_eval { attr_accessor :first_name }
      @first_name = nil
      singleton_class.class_eval { attr_accessor :last_name }
      @last_name = nil
      singleton_class.class_eval { attr_accessor :username }
      @username = nil
    end

    read { ... }
    update { ... }
    delete { ... }

    element 'ID' do
      get { |opts| @parent.id }
      set { |value| @@parent.id = value }
    end

    element 'First Name' { ... }
    element 'Last Name' { ... }
    element 'Username' { ... }
  end
end

Invoke the Workflow

To define the workflow layer, let’s edit demo.rb. First we need to require Vidalia:

require 'vidalia'

Next we want to load our object definition.

require './page'

Before we go any further, we also need to pass a logging routine to Vidalia. This routine just tells Vidalia how to log a given string. For our demonstration, we’ll just print to stdout:

Vidalia::set_logroutine { |logstring|
  puts logstring
}

For the purposes of our example, we need to pull in the database defined by db_helper. This will use the sqlite3 gem (which is not needed for Vidalia, but needs to be installed to run this test) to build an in-memory database of user data for us to work with. Note that we can now use the log routine we just defined.

Vidalia.log("Building the database for this test")
require './db_helper'

Now we can get the Interface we defined in user_object.rb instantiated into an object:

db = Vidalia::Interface.get("Application DB")

Finally, we can instantiate our user object:

user_object = db.object("User")

Now we get to the good stuff! With all the setup behind us, it’s now pretty straightforward to use our interface. Let’s start by reading a user record. We’ll the user with an ID of 7, who we happen to know is Art VanDelay:

user_object.read(:id => 7)

Now user_object contains all of the data for the user with ID=7. We can now use the verify directive to confirm that it contains what we expected it to contain:

user_object.element("First Name").verify "Art"
user_object.element("Last Name").verify "VanDelay"
user_object.element("Username").verify "realarchitect"

As described above, these directives will log that they have successfully verified the values or they will raise an exception if they fail.

With our user data in hand, now we can manipulate it:

user_object.element("First Name").update "George"
user_object.element("Last Name").update "Costanza"

Then save our changes to the database:

user_object.update

Again, we can verify that our update worked with a quick read from the database and a call to verify each element of the object:

user_object.read(:id => 7)
user_object.element("ID").verify 7
user_object.element("First Name").verify "George"
user_object.element("Last Name").verify "Costanza"
user_object.element("Username").verify "realarchitect"

As another example, we can call the delete method to remove the object from the database:

user_object.delete(:id => 7)

And finally, we can demonstrate the confirm directive by attempting to read the deleted object and confirming that nothing was returned:

user_object.read(:id => 7)
if user_object.element("ID").confirm nil
  Vidalia.log("Object was successfully deleted!")
else
  raise "Delete didn't work!  Object read came back with data!"
end

This is a simple example, but hopefully it demonstrates the power and flexibility Vidalia gives you in managing interfaces and objects and using them in the workflow layer of your test automation.

About

Vidalia uses layers to simplify the creation and maintenance of your automated API and database test suite.

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages