EasyMock matchers on Boolean null

November 20th, 2013

Given an interface:

public interface Fancy {

    public void doSomething(Integer x, Boolean compute);

}

The following will not match:

Integer x = 5;
Boolean compute = null;
expect(
        mockFancyObject.doSomething(
                eq(x),
                eq(compute)
        );

In fact, if variables are not used, a null value inserted directly will not compile:

expect(mockObject.doSomething(
                eq(x),
                eq(null)
        );

Easy way to continue using data-driven tests where variables are passed and the Boolean values could be null:

expect(mockFancyObject.doSomething(
                eq(x),
                compute == null ? isNull(Boolean.class) : eq(compute)
        );

My Introduction to RDF

April 27th, 2013

A friend recently asked me how Indie Shuffle has gathered their data. She was probably wondering if they meticulously gathered this data on their own, or if the aggregation was automated. She is likely interested because she would like to publish blog entries that use resources from other publishers. This question is timely, as I recently attended a No Fluff Just Stuff conference, where Brian Sletten introduced to W3C’s Resource Description Framework and Resource Description Framework – in – attributes. I later purchased “Linked Data: Structured Data on the Web” as a follow-up. This introductory RDF and RDFa exploration serves to provide an answer to her question.

First, a brief intro into what we need to know about RDF and RDFa for this investigation.

RDF is a group of specifications published by W3C to provide a machine readable way for content publishers to describe resources and their relationships to other resources, and for consumers to, well, consume that content. The core of the RDF is the RDF statement. RDF statements provide resource relationships, where a single RDF statement consists of the Entity being described, its Attribute, and that attribute’s Value. The Entity-Attribute-Value (EAV) is often alternatively described as Subject-Predicate-Object, which I find to be more intuitive.

RDFa provides a way to decorate HTML documents with RDF statements. In this way, resources available only as an HTML Content Type can be analyzed through RDF (as opposed to the publisher providing a representation of the same resource using an RDF Content Type ).

As a Chrome user, my first step was to install the Chrome RDF Detector as an aid. Viewing the site with this plugin, I was at first optimistic that Indie Shuffle published their sources through RDFa (assuming they are even using RDF to gather information), as the plugin detected RDFa in the HTML document, and a click on the link provided by the plugin opened an RDFa inspector URL with the Indie Shuffle URL passed as a query parameter. Alas, their usage of RDFa appears to be purposed primarily for CSS, their Facebook info, and bookmarks to their own related pages, which use RDFa in the same manner.

Ok, so Indie Shuffle doesn’t get us what we want: a way to find and include other publishers’ content within our own. We will need to try another route, for which I will post a follow-up.

B.D.D. Testing with Jython Using cucumber-jvm

April 1st, 2012

Very recently, cucumber-jvm 1.0 was released, providing a Java implementation of  the Cucumber B.D.D. test framework. Jython is one of many languages supported by this new implementation. This brief HOWTO explains how to add the cucumber-jvm jar to a Jython installation and use it to drive your specifications. Any libraries in your Jython environment will be available for your step definitions.

This assumes you have first installed Jython

I have created an environment variable for my Jython install path in ~/.bash_profile, which will be used throughout this HOWTO:

export SYS_JYTHON_INSTALL="/Users/stephenabrams/tools/jython2.5.2"

Installation

Download and run ez_setup.py to install setuptools:

~$ wget http://peak.telecommunity.com/dist/ez_setup.py
~$ jython ez_setup.py

Install pip:

~$ which jython # shows jython install location
~$ sudo jython $SYS_JYTHON_INSTALL/bin/easy_install pip

Install virtualenv:

~$ sudo jython $SYS_JYTHON_INSTALL/bin/pip install virtualenv

Create a virtual environment for a Jython project. Do the following from a location where you’d like to store your virtual environment’s libraries. This will take care of installing setuptools and pip in your virtual environment automatically.

~$ jython $SYS_JYTHON_INSTALL/bin/virtualenv jython-cucumber-jvm-example

Activate the virtual environment, ensuring library installations will be placed in the it alone:

~$ source jython-cucumber-jvm-example/bin/activate
~$ which jython # this should now point to the virtual env version of jython

Install jip to the virtual environment:

~$ which pip # sanity check, this should be virtual env pip
~$ pip install jip

Finally, install cucumber-jvm from Maven:

~$ jip install info.cukes:cucumber-jython:1.0.1

Take a peek at the jython-cucumber-jvm-example environment configuration directory to see how the cucumber-jvm jars were included.

Example Usage

Lets create a new project, and locations for our specifications and step definitions:

~$ cd workspace
~/workspace$ mkdir hello_world; cd hello_world; mkdir features; mkdir features/step_definitions; mkdir src

Create a utility Jython script for running our features called hello_world/cucumber-jvm.py:

#!/usr/bin/env jython
import sys
from cucumber.cli import Main

Main.run(sys.argv[1:], Main.getClassLoader())

For our first feature, open features/hello_world.feature and write:

Feature: Good old Hello World

  Scenario: Someone starts their day
    Given a person
    When that person wakes up
    Then that person says "Hello, World!"

Now let’s see it run. We use “jython-all” instead of jython to include the cucumber jar files installed with jip.  Note that we don’t have any step definitions in features/step_definitions/ or code to test in src/ yet, but we’ll specify the “glue” code path for later:

~/workspace/hello_world$ jython-all cucumber-jvm.py --glue features/step_definitions --glue src features

You should now see some messages about the processing of the jars, and then the standard Cucumber output where the step definitions are missing:

*sys-package-mgr*: processing new jar, '/Users/stephenabrams/jython-cucumber-jvm-example/javalib/cucumber-core-1.0.1.jar'
*sys-package-mgr*: processing new jar, '/Users/stephenabrams/jython-cucumber-jvm-example/javalib/cucumber-html-0.2.1.jar'
*sys-package-mgr*: processing new jar, '/Users/stephenabrams/jython-cucumber-jvm-example/javalib/cucumber-jython-1.0.1.jar'
*sys-package-mgr*: processing new jar, '/Users/stephenabrams/jython-cucumber-jvm-example/javalib/gherkin-2.9.3.jar'
UUU

You can implement missing steps with the snippets below:

@Given('^a person$')
def a_person():
  # Express the Regexp above with the code you wish you had
  raise(PendingException())

@When('^that person wakes up$')
def that_person_wakes_up():
  # Express the Regexp above with the code you wish you had
  raise(PendingException())

@Then('^that person says "([^"]*)"$')
def that_person_says(arg1):
  # Express the Regexp above with the code you wish you had
  raise(PendingException())

Great success! Now lets copy these steps and put them in features/step_definitions/steps.py and run it again. We now see:

Traceback (most recent call last):
  File "/cucumber/runtime/jython/dsl.py", line 34, in execute
TypeError: a_person() takes no arguments (1 given)

Interestingly, The Jython steps require “self” to be the first arg but the snippet generator doesn’t include it. Maybe this is a bug in the new framework, but for now lets just add “self” to each step def:

@Given('^a person$')
def a_person(self):
  # Express the Regexp above with the code you wish you had
  raise(PendingException())

@When('^that person wakes up$')
def that_person_wakes_up(self):
  # Express the Regexp above with the code you wish you had
  raise(PendingException())

@Then('^that person says "([^"]*)"$')
def that_person_says(self, arg1):
  # Express the Regexp above with the code you wish you had
  raise(PendingException())

Now we run again:

P--

cucumber.runtime.PendingException: TODO: implement me
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        ...

Here an exception is thrown since these are still pending steps (stack trace truncated above). Let’s update the step definitions with some assumptions about our Person class interface:

@Given('^a person$')
def a_person(self):
  self.person = Person()

@When('^that person wakes up$')
def that_person_wakes_up(self):
  self.last_response = self.person.wake_up()

@Then('^that person says "([^"]*)"$')
def that_person_says(self, arg1):
  if (arg1 != self.last_response):
    raise(Exception("person said '%s' not '%s'" % (self.last_response, arg1)))

And let’s create the class under test:

class Person:
  def wake_up(self):
    return "Hit the snooze!"

And run again. Now Cucumber reports back:

..F

Traceback (most recent call last):
  File "/cucumber/runtime/jython/dsl.py", line 34, in execute
  File "steps.py", line 12, in that_person_says
Exception: person said 'Hit the snooze!' not 'Hello, World!'

Ahem… lets fix Person:

class Person:
  def wake_up(self):
    return "Hello, World!"

The next run passes, yielding three green dots.

I have also tested and verified that modules included in the Jython Lib directory are accessible from both the code under test and the step definitions.

Sources:

http://www.jython.org/jythonbook/en/1.0/appendixA.html#setuptools

http://stackoverflow.com/questions/6787015/how-i-install-various-python-libraries-in-jython

http://pypi.python.org/pypi/virtualenv

http://packages.python.org/django-jython/quickstart.html

https://docs.djangoproject.com/en/dev/topics/db/models/#quick-example

http://pypi.python.org/pypi/jip

Rails Authentication with Devise and OmniAuth

February 23rd, 2012

My application uses Devise for authentication, and recently I wanted to add login support via social networking applications, to pave the way for seamless content creation on those applications by my users. The Gherkin scenarios below express the authentication behavior, augmented with the new requirements to authenticate via social network credentials:

Feature: Authentication
  In order to publish content
  As a music enthusiast
  I want to be able to log in via username and password or by social networking creds

   Scenario: User logs in with email and password
    Given I have a user account with email: "someemail@email.com", password: "password"
    When I navigate to the session login page
    And I fill in "user_email" with "someemail@email.com"
    And I fill in "user_password" with "password"
    And I press "Sign in"
    Then I should see "Signed in successfully."
 
  Scenario: User logs in with Facebook account
    Given I am a facebook registered user on this app
    When I navigate to the session login page
    And follow "Facebook"
    And I follow Facebooks procedure to use my Facebook account with this application
    Then I should see "Signed in successfully."
  
  Scenario: Logged in user associates their Facebook account
    Given I am an email-registered user
    And I am authenticated
    And I have a Facebook account
    When I navigate to the authentications page
    And follow "Facebook"
    And I follow Facebooks procedure to use my Facebook account with this application
    Then I should see "Successfully associated your facebook account with your local account."
    And I should be a Facebook-registered user
    
  Scenario: New user logs in with Facebook account
     Given I am not authenticated
     And I have a Facebook account
     When I navigate to the session login page   
     And follow "Facebook"
     And I follow Facebooks procedure to use my Facebook account with this application
     Then I should see "Signed in successfully."

To do this, I followed the Railscasts using Devise and OmniAuth, found here and here. After making these changes, I found it helpful to map out the interactions between client, web application, and social network. The role of the registrations controller, which was extended in the Railscast to handle login cases where user validation fails, has been omitted from this discussion.

For initial integration, I used Facebook instead of Twitter – Facebook allows me to use “localhost” in the callback url, useful for local development and testing (all requests must be made on the dev. machine).

The first UML diagram below shows the basic interaction required between client, app, and Facebook (a similar UML can be found on their developer’s site)

Facebook Interaction

The diagram excludes any extra interactions between the user and Facebook. These excluded interactions must be handled by Facebook, and they will vary depending on the state of the user, primarily with regard to the user’s Facebook login state, and whether or not they have granted my application access to their data in the past.

The play by play:

  1. CLIENT makes request to APP that requires authorization. The user is not authenticated, so APP redirects the user to a login page.
  2. CLIENT elects to authenticate via FACEBOOK. APP redirects CLIENT to FACEBOOK to do so, including APP’s Facebook key and the callback url to APP to be used later:
    https://graph.facebook.com/oauth/authorize?response_type=code&client_id=1234567890&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Ffacebook%2Fcallback&scope=email%2Coffline_access
  3. CLIENT makes the call to FACEBOOK for authorization.  FACEBOOK redirects CLIENT back to APP, but with a code that APP will use to gain access to the user’s data on Facebook:
    http://localhost:3000/auth/facebook/callback?code=ReaLlYLoNgCodEHERe#_=_
  4. CLIENT makes the new call to APP. APP in turns calls FACEBOOK with the code in order to get an access token and the user’s data. FACEBOOK reads the code and recognizes its validity, responding with the user’s data. APP looks in its database to find the user based on the userid passed back from FACEBOOK. If non-existent, a new user is created and in the users table and an entry is made in the authentications table, linking the Facebook userid to the new user.  Omniauth data is added to the user’s session, and a cookie is sent on the response back to CLIENT. In future calls, the CLIENT will use this cookie when making requests that require authorization, and will be honored so long as the session exists.

When the application is broken up by controller to show this interaction:

Controller UML

This demonstrates the roles of each controller:

Devise Session Controller: Responsible for serving login pages and handling traditional username/password submissions to create a session. The rendered pages include links for third party authentications through the Authentications Controller.

Authentications Controller: Extending Devise’s Registrations Controller, this is responsible for interacting with the social networks. It will create users when needed and associate social network accounts with the local user.

Adding Debugger to Cucumber

January 22nd, 2012

While trying to understand why a Cucumber feature file was failing, I wanted to look at object states with a step definition on a non-Rails project. To do this, I included the following in my Cucumber env.rb file:

require 'ruby-debug'

and then, as required by ruby-debug gem, added “debugger” where I wanted to pause:

When /^some step definition$/ do
  debugger # this will pause Cucumber
end

When running Cucumber from the command line, there is now a pause. When I type “irb” from here, I can investigate the current state from within this step definition.

This was done while running ruby 1.8 with the ruby-debug gem installed. For Ruby 1.9, be sure to install ruby-debug19.

Setting up BDD Environment for Ruby on Rails

November 20th, 2011

To facilitate behavioral driven (B.D.D.)and test driven development (T.D.D), I opted to use Cucumber for B.D.D. and Rspec for T.D.D. After running both of these manually for a while, I grew tired of finding broken tests well after the code breaking the test was written. To include automatic regression checks on my development machine, I chose to use:

  • Spork – test server that will load configured aspects of your application only once, allowing for tests to be executed without a full setup on each run
  • Guard – system event processing utility. In our case, this will allow project file changes to initiate new test runs and notify the developer of the results
  • Guard-Spork - gem that allows easy integration of application tests (RSpec, Cucumber, etc.) with Spork using Guard
  • Growl  - A Mac OS notification system, which will be the application used by Guard to present a test result notification
  • guard-rspecguard-cucumber
Assuming a Rails 3 environment with RSpec and Cucumber and a Mac OS with Growl installed, this local continuous integration environment can be set up fairly easily.
First, update the application’s Gemfile to add these gems to the test and development environments.
group :development, :test do
#...
gem 'rb-fsevent'
gem 'growl_notify'
gem 'ruby_gntp'
gem 'growl'
gem 'guard-spork'
gem 'guard-rspec'
gem 'guard-cucumber'
#...
end

Install Guard via gem installer in order to get the guard command line utility.

gem install guard

Update gems based on the Gemfile.

bundle install

Generate boiler plate code within the application.

guard init spork
guard init rspec
guard init cucumber

Check the new Guardfile in the application and verify that the desired files and directories will be watched for updates. For example, I found that the Cucumber configuration was not watching the app directory, so I added this.

guard 'cucumber' do
  watch(%r{^app/(.+)}) { 'features' } # added manually
  watch(%r{^features/.+\.feature$})
  watch(%r{^features/support/.+$}) { 'features' }
  watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
end

Now start the continuous test environment.

bundle exec guard

We should now seeing RSpec and Cucumber are running on the command line, and should receive a Growl notification.

Developing in the new environment

Now that the environment is up and running, we can do T.D.D. easily. Before this environment setup task, I had a pending Rpsec test for my Event model:

  it "should return only events for a certain time range" do
    pending
  end

When I replace the pending test case with an actual test, like so:

it "should return only events for a certain time range" do
  time = Time.now
  create(:event, {:time => time.years_ago(2) })
  create(:event, {:time => time + 3.weeks.to_i})
  # in range
  expected_events = []
  expected_events << create(:event, {:time => time})
  expected_events << create(:event, {:time => time.end_of_day})
  expected_events << create(:event, {:time => time.beginning_of_day})
  expected_events << create(:event, {:time => time.yesterday})
  expected_events << create(:event, {:time => time + 1.weeks.to_i})
  results = Event.between(time.yesterday.beginning_of_day, time + 2.week.to_i)

  results.size.should eql 5
  results.should include *expected_events
end

And save the spec file, guard notices the update and re-runs the Rspec suite with the updated files. The test fails, Growl is notified and displays an error on my screen:

and the console displays the error:

 

This test is failing because there is no “between” method yet. Now we write the method (as a scope within the Event model):

  scope :between, lambda { |start, finish| where("time >= ? AND time <= ?", start, finish) }

And upon save, Growl shows a notification that all tests passed, and one test is still pending (we started with two pending tests):

Quick fix for lost write ability on non-journaled HFS formatted disk, mounted on Ubuntu

July 31st, 2011

I use an external USB drive on my Ubuntu system to store the music I stream from my Subsonic server. I chose to format with HFS on a mac in case I wanted to ever just unplug the disk for use with my Macbook. Following the steps to get HFS filesystem mounted with r/w permissions on Ubuntu, I had this working, but after a while I lost the write ability. I found out what was happening with a little help from this and this.

Sure enough, this disk was flagged:

sabrams@ubuntu-server:/mnt/usb_drive$ dmesg|grep hfs
[   11.772113] hfs: Filesystem was not cleanly unmounted, running fsck.hfsplus is recommended.  mounting read-only.

A quick look at my /etc/fstab:

# /etc/fstab: static file system information.
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    defaults        0       0
# /dev/sda1
UUID=00cf2e7a-600f-4885-8b65-f42dd91be4b3 /               ext3    relatime,errors=remount-ro 0       1
# /dev/sda5
UUID=61c68702-10e6-47e4-bbac-63b2c0b8f128 none            swap    sw              0       0
/dev/scd0       /media/cdrom0   udf,iso9660 user,noauto,exec,utf8 0       0
/dev/sdb2 /mnt/usb_drive  hfsplus rw,exec,auto,users 0 0

allowed me to repair with the following (unmount, repair, execute /etc/fstab w/o reboot, test):

sabrams@ubuntu-server:/mnt/usb_drive$ sudo umount /mnt/usb_drive/
sabrams@ubuntu-server:/mnt/usb_drive$ sudo fsck.hfsplus /dev/sdb2
sabrams@ubuntu-server:/mnt/usb_drive$ sudo mount -a
sabrams@ubuntu-server:/mnt/usb_drive$ touch /mnt/usb_drive/test.txt
sabrams@ubuntu-server:/mnt/usb_drive$ ls /mnt/usb_drive/
Foals  Frost_Heaves_Sessions  Surfer Blood  test.txt  The Kills  The Strokes

Simple Guitar Pedal Board HOWTO

July 13th, 2011

Here’s a way to quickly make a fairly inexpensive pedal board (actually about $65, with leftovers available for other projects):

Parts needed (everything is available at Home Depot, though probably cheaper online):
1. 1/4″ width MDF wood (I purchased 2′X4′ for $5.23
2. Industrial Strength Velcro (lots of leftover after this) ($16)
3. Velcro One-Wrap (~$8)
4. Visual Sound One Spot Combo Pack (~30) (You will want to verify that your pedals are supported with this unit. My Line 6 delay, shown below, is plugged in separately.)
3. Black duct tape

The only tools required are a saw, straight-edge, and a pencil.

Steps.

1. Choose dimensions based on your needs (lay out your pedals and draw the dimensions needed on the board).
2. Cut the board to size.
3. Wrap in black duct tape.
4. Layer the soft side of the velcro over the entire board, or select areas depending on preference.
5. Apply the other side of the velcro to the bottoms of your pedals (removing feet from pedals if needed).
6. Use the One Wrap to bundle cables (such as extra length from the One Spot), attaching them to the board.

Integrating Jackson JSON Serializer/Deserializer into Android project

July 11th, 2011

My web application supports a service API that returns output in a few possible formats, JSON being one of them. For my Android application to make use of this API, it must be able to consume JSON data. There are several JSON serialization/deserialization (that is, converting the JSON data to a real object, or converting a real object into JSON data) packages out there, with Jackson and GSON being popular options. For this project, I choose Jackson only because I’ve tried a subset of GSON functionality on a work project and figured I’d mix it up a bit. In at least the subset case that I’ve used at work (data-binding), the APIs are pretty similar.

Jackson supports three methods of JSON serialization, all of which are described on their primer page. While not the most efficient, the quickest way to get up and running is to use the data binding method. In this way, I can define the domain classes and instantiate domain objects from incoming JSON without any additional coding. If we need to optimize later, we can switch to the streaming API.

First, downloading Jackson. The entire package can be downloaded in one bundle, but for the sake of keeping my application size small (perhaps I’m being pedantic), I am only downloading what is necessary for the data binding method. The links are to 1.8, the latest stable version at the time of this writing. I chose ASL, though I didn’t investigate licenses since I won’t be redistributing:

core-asl
mapper
mrbean (This is for dynamically creating the domain classes on the fly – I probably won’t keep this, and isn’t used in this exercise, but I want to try out dynamic class creation)

Once downloaded, import them into the Android project, by right-clicking on the project in the package explorer, selecting properties, selecting the “Libraries” tab, going to “Java Build Path”, and adding each by pressing the “Add External JARs” button:

Jackson JARs imported

We may be dealing with elements from these libraries in our unit tests, and it will not work to add the libraries to the test project in the same fashion, so select these libraries for export as shown:

Export Jackson libraries

Now, let’s start out expecting a class with class method “deserializeEvent” to accept an input stream that will pass JSON (since we’re going to be consuming web API data) representing an event (a new class) and return a deserialized object. So, our test class, created in the separate Android test project:

package com.clogs.test.json;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;

import android.test.AndroidTestCase;

import com.clogs.domain.Event;
import com.clogs.json.JsonUtil;

public class JsonUtilTest extends AndroidTestCase {
 
  public void testDeserializeEvent() throws JsonParseException, JsonMappingException, IOException {
    String eventJson = "{\"name\":\"fancy event\"}";
    InputStream is = new ByteArrayInputStream(eventJson.getBytes("UTF-8"));
    Event event = JsonUtil.deserializeEvent(is);
    Event expectedEvent = new Event();
    expectedEvent.setName("fancy event");
   
    assertEquals(expectedEvent, event);
   
  }
}

In our main project, we create the Event class. Note that the equals/hashcode methods have been overridden, as we want “equals” to mean that all properties are the same when comparing two event instances (to be used in the unit test):

package com.clogs.domain;

public class Event {
 
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    Event other = (Event) obj;
    if (name == null) {
      if (other.name != null)
        return false;
    } else if (!name.equals(other.name))
      return false;
    return true;
  }

}

And finally, the JSON utils class under test:

package com.clogs.json;

import java.io.IOException;
import java.io.InputStream;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.mrbean.MrBeanModule;

import com.clogs.domain.Event;
import com.clogs.domain.Model;

public class JsonUtil {
 
  private static final ObjectMapper mapper = new ObjectMapper();

  public static Event deserializeEvent(InputStream is) throws JsonParseException, JsonMappingException, IOException {
    return mapper.readValue(is, Event.class);
  }
}

This unit test passes, and verifies that basic Jackson full data binding is running correctly in the Android application.

Creating a Test Project for an Android Application

July 9th, 2011

Google documents how to create a separate project from your Android application that will contain and run your tests. Their framework extends JUnit’s functionality to include more utilities and instrumentation that will run your tests within the context that your application will exist (i.e. emulator, device).

They have provided tools within Eclipse to make it a trivial task to get started, allowing us to use Test Driven Development from the start. The new test project can be created by performing the following:

1. Create a new Android test project via the Android plugin utils for Eclipse. Right-click your project, select “Android Tools” and “New Test Project”.

Creating an Android Test Project Step 1


2. Choose a name for your test project (JUnit test classes are usually named by appending “Test” to whatever class they are testing. I chose the same convention for the project name.)
3. Choose to test “An existing Android project”, and select your project.

Creating and Android Test Project Step 2

Now that we have a test project created and already configured to test the application, let’s try a test.

We will expect a class called Speaker to return “hello!” when static method sayHello() is called.

First, create a simple test class in the test project. This class extends AndroidTestCase, which in turn extends JUnit’s TestCase.

package com.clogs.test;
import com.clogs.Speaker;
import android.test.AndroidTestCase;

public class TestSpeaker extends AndroidTestCase { 
  public void testSayHello() {
    assertEquals("hello!", Speaker.sayHelloWorld());
  }
}

Then, create the Speaker class in the application project:

package com.clogs;
public class Speaker {
  public static String sayHello() {
    return "hello!";
  }
}

Now we can right-click our way to a test run.

Run Test

The emulator will fire up (assuming proper configuration), and our results will be presented in the JUnit view.

Test Results


This site is protected by Comment SPAM Wiper.