Mar 11

In this post, I will talk about creating nicer syntax for verifications.

In Ruby, we can add new methods into existing class. Rspec adds some nice assert methods such as should_equal(expected_string) into string class. However, it is impossible to do it in Java. Right, we can’t do it in unit test. Fortunately, we are writing automated tests. we can control what to return. Hence, I write a wrap class to allow code to use rspec-like syntax.


public class TextDSL extends Assert {
private String text;
private String errorMessage;

public TextDSL(String text) {
this(text, "");
}

public TextDSL(String text, String errorMessage) {
this.text = text;
this.errorMessage = errorMessage;
}

public void shouldEqual(String expectedText) {
assertEquals(errorMessage, expectedText, text);
}

public void shouldNotEqual(String notExpectedText) {
assertFalse(errorMessage + " not expected:<" + notExpectedText + "> but was:<" + text + ">", notExpectedText.equals(text));
}

public void shouldEqual(int expectedInteger) {
assertEquals(errorMessage, String.valueOf(expectedInteger), text);
}

public void shouldNotEqual(int notExpectedInteger) {
assertFalse(errorMessage + " not expected:<" + notExpectedInteger + "> but was:<" + text + ">", String.valueOf(
notExpectedInteger).equals(text));
}

public void shouldInclude(String expectedIncludedText) {
assertTrue(errorMessage + " expected included:<" + expectedIncludedText + "> but was not included in:<" + text + ">",
text.contains(expectedIncludedText));
}

public void shouldNotInclude(String expectedNotIncludedText) {
assertFalse(errorMessage + " not expected included:<" + expectedNotIncludedText + "> but was included in:<" + text + ">",
text.contains(expectedNotIncludedText));
}
}

then we can get nicer syntax to express our intent for verification.

dropDownList.shouldDisplayLabel -> dropDownList.lable().shouldEqual(expectedLabel)

dropDownList.shouldDisplayIndex -> dropDownList.index().shouldEqual(expectedIndex)

and I can do something like verify css style includes a particular style:

dropDownList.css().shouldInclude(”errorField”)

We can add more methods to TextDSL class. or we can create other DSL class we want to verify the object we want.

Later on, I will post the source code so that you can see the whole implementation.

Feb 27

I was working in a project which used Selenium RC as web test tool. The functional test coverage is high. However, as the application has become more complicated and more tests have been added, the automated testing becomes a bottleneck for velocity. The problems?

  • Hard to understand the intent of automated tests
  • Duplication of Selenium test code
  • Hard to reuse
  • Page objects has all responsibilities of page operations and verifications
  • Test the wrong thing
  • Because of complexity, fewer scenarios are created
  • Overtesting cause performance problem
  • New team members find it hard to write tests
  • QAs take a long time to write automated tests

Let’s see how to solve all the problems listed above.

In the project, developers and QAs are both writing selenium tests. Without navigator into the code, it’s very hard to understand what does the particular test do.One way to solve the problem is to name the test better. for example testShouldAllTestFieldSetMaxLengthAsWhatTheyExpected(). However, selenium tests is not unit test, sometime it’s not easy to give a name to define all the operations and verification of the tests.

Recently, DSL became a hot topic. Define DSL in automated tests would help develops and QAs (even BAs) understanding the intent of all tests. Hence, I decide to define DSL for elements in the page object.

The advantage to use DSL as following:

  • Easier to read
  • Precise error message, i.e.:
    • assertTrue(selenium.isElementPresent(”id”) will give you message”expect true, but false”, what the heck does that mean when you see the error message?
    • element.shouldExist() will give you message “expect element ### exist, but not exist”, you can customized the error message you want.
  • Reuse DSL
  • No more asserts in the test class
  • Quicker development
  • Precise testing

Here I show some simple DSL. (two types: operations and verifications)

TextBox:

  • type(String text)
  • shouldDisplayRedBorder(), shouldNotDisplayRedBoder()
  • shouldDisplayValue(String expectValue)
  • shouldBeReadOnly(), shouldNotBeReadOnly()
  • shouldBeDisabled(), shouldNotBeDisabled()
  • shouldExist(), shouldNotExist()
  • ……

Radio:

  • click()
  • shouldBeSelected(), shouldNotBeSelected()
  • ……

DropDownList:

  • selectLabel(String label)
  • sleectIndex(String index)
  • shouldDisplayLabel(String expectedLabel)
  • shouldDisplayIndex(int index)

Checkbox, Label, ErrorIcon, Table…

Depends on the project, you can create more customized element suitable for your need.

In page objects, we just need to return element object. for example:


class AmazonPage

public searchInDropDownList() { return newDropDownList("url"); }

public fieldKeywordTextbox() { return newTextbox("field-keyword"); }

public searchButton() { return newButton("go"); }

}

then the test become look like


amazonPage.searchInDropDownList().selectLabel("Books");

amazonPage.fieldKeywordTextbox().type("Selenium");

amazonPage.searchButton().click()

self-documentation is better than having a lot of comment in the test.

In selenium DSL II, I will talk about how to define better DSL syntax.

Jan 03

Sometime I want to use a ruby library and implement something in ruby code so I could use the cool ruby features. JRuby provide the capability to run ruby code in Java. Therefore I could implement Java interface in Ruby.

1. Create the Java Interface

public interface Animal
{
public String speak();

public String move();
}

2. Implement the interface in Ruby


class Animal
def move
"Move..."
end

def speak
"Speak..."
end
end

3. Create a factory to create the concrete class of the interface


import org.jruby.Ruby;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.javasupport.JavaEmbedUtils;

import java.lang.reflect.Method;
import java.io.InputStream;
import java.io.File;

public class RubyFactory
{
public static final String RUBY_src="ruby";

public static <T> T getBean(Class<T> type)
{
Ruby runtime = Ruby.getDefaultInstance();

try
{
runtime.evalScript(runtime.evalScript("File.open('" + RUBY_SRC + File.separator + type.getSimpleName().toLowerCase() + ".rb').read").toString());
runtime.evalScript(extendRubyScript(type));
}
catch (Exception e)
{
System.err.println(e.toString());
}

Object c = runtime.evalScript(type.getSimpleName() + ".new");
c = JavaEmbedUtils.rubyToJava(runtime, (IRubyObject) c, type);
return (T) c;
}

private static String extendRubyScript(Class type)
{
String rubyScript = "require 'java'\n" +
"class " + type.getSimpleName() + "\n" +
"  include Java::" + type.getName().replace(".", "::") + "\n";

Method[] methods = type.getMethods();

for (Method method : methods)

{
String name = method.getName();
rubyScript += "  eval(\"alias " + name + " #{'" + name +        "'.gsub(/([A-Z]+)([A-Z][a-z])/,'\\1_\\2').gsub(/([a-z\\d])([A-Z])/,'\\1_\\2').tr('-', '_').downcase}\")\n";
}

rubyScript += "end";
return rubyScript;
}    public static void main(String[] args)
{

Animal animal = RubyFactory.getBean(Animal.class);
System.out.println(animal.move());

System.out.println(animal.speak());
}
}

All the ruby files should be put into the ruby directory. I follow the naming convertion as following:

interface Greeting -> greeting.rb with class Greeting

method sayGoodbye in java -> method say_goodbye in ruby

4. use the factory above, we could easily to implement another java interface.


public interface Greeting
{
public String sayGoodbye();

public String sayHello();
}

5. The implementation of interface greeting.


class Greeting
def say_hello()
"Hello"
end

def say_goodbye()
"Goodbye"
end
end

6. Test Greeting.


public class GreetingTest extends TestCase
{
private Greeting greeting;

@Before
protected void setUp() throws Exception
{
greeting = RubyFactory.getBean(Greeting.class);
}

@Test
public void testSayHello()
{
assertEquals("Hello", greeting.sayHello());
}

@Test
public void testSayGoodbye()
{
assertEquals("Goodbye", greeting.sayGoodbye());
}
}

In order to run the application, you need to include asm-2.2.3.jar, asm-commons-2.2.3.jar, backport-util-concurrent.jar and jruby-complete-1.0.1.jar

download source code