Thursday, January 31, 2008

Desktop Android


This thread asks an interesting question: is it possible to run Android UI on desktop ?
Well, I think that is possible but very difficult, and almost impossible if the source code is not available. I said almost, because as the well know sports brand remind us: impossible is nothing.

In the meantime, this screenshot shows how using tweaked skins you can run the emulator in a native window, even at a bigger size. This one is 640x480.

There's no special keys or buttons (dpad) here, so you must remember this



















F1/PgUp Menu key
F2/PgDown Star key
F3 Call key
F4 End Call key
Home Home key
ESC Back Key
F7 Power button
F8 Disable/Enable all networking
F9 Start tracing (only with -trace)

F10 Stop tracing (only with -trace)
Keypad 2468 Dpad arrows
Keypad 5 Dpad center
Keypad 79 Rotate device skin
Keypad + Volume Up key
Keypad - Volume Down key
Keypad / Decrease onion alpha
Keypad * Increase onion alpha
Ctrl-K Switch between 'unicode' and 'raw' keyboard modes

Download the WINDOW-L and BIGWINDOW-L skins from http://codtech.com/downloads/android-window-l-skins.zip, unzip it in some directory of your preference (/home/android/skins in the following example) and then run the emulator

$ emulator -skindir /home/android/skins -skin BIGWINDOW-L

Tuesday, January 29, 2008

Android Radio Logger: The code is here

You may be wondering how to obtain incoming and outgoing voice and sms events data, mainly the phone number.
Well, finally you can download the code for this workaround from http://codtech.com/downloads/AndroidRadioLogger.zip
A quick and dirty solution, to keep you concentrated on the application being developed and not on how to hack android.

Run Radio Logger, and if you get the error

java.io.FileNotFoundException: /tmp/radio

don't forget to obtain a shell in the emulator running

adb shell

and once in the emulator prompt run

# cat /dev/log/radio > /tmp/radio & ### note the final ampersand

and eventually re-launch the Radio Logger application again.

When this application is running you will be able to see small notification windows at the bottom right of the screen indicating the GSM event detected, actually incoming and outgoing voice and sms, as is showed in the video. Other events should be easily added.

As always, comments are gladly welcome.

Friday, January 25, 2008

Android Radio Logger

A fancy name for a dirty hack...
It seems that there's no way to intercept android's call to obtain the incoming or outgoing number
as discussed in this thread.

Well, until now
video

The notification at the lower right, showing the GSM events detected is provided by the Radio Logger Activity.
Source code will be published soon, after fixing a couple of issues.
Drop me a line if you can't wait.

Thursday, January 24, 2008

Android Positron small problems fixed



If you have followed the previous tutorial: http://dtmilano.blogspot.com/2008/01/test-driven-development-and-gui-testing.html, and wanted to use positron-0.5-alpha for your tests you may have found two small problems:
  1. positron-0.5-alpha.jar is empty, in the positron downloads
  2. an IllegalArgumentException is generated while running the tests
Solution
Build positron-0.5-alpha.jar from sources and apply the patch http://code.google.com/p/android-positron/issues/attachment?aid=-2700323565965776159&name=android-positron-databasedir-svn.patch

This was already reported to positron:
  1. http://code.google.com/p/android-positron/issues/detail?id=1
  2. http://code.google.com/p/android-positron/issues/detail?id=2

Wednesday, January 23, 2008

Test Driven Development and GUI Testing on the Android platform: Temperature Converter sample

Google Docs: This article can be viewed at Test Driven Development and GUI Testing on Android platform: Temperature Converter sample

Unit tests

This article will introduce Test Driven Development on the Google Android platform. I took me some time to figure it all out and it's not always clear where to find the correct documentation, answers or resources, so I'm writing this hoping it be helpful for someone who is trying to take this approach.

This is an open topic, an we are all doing the first steps and trying to discover some functionality which is still not documented in the Android SDK, and up to now there's no sources to check what's the intention of some API code or tools.

We will be using a fairly simple example that was used in previous articles about similar subjects. That's a Temperature Converter application. Requirements are defined by our Use Case: Convert Temperature.

Using Test Driven Development we will implement this use case, trying to keep it as simple as possible not to get confused with unnecessary details.


Comments, corrections, improvements, critics are gladly welcome.

Project

  1. Create a Android project project in Eclipse (File->New Project...->Android->Android Project)
  2. Name the project something like TDD Android
  3. Package name: com.example.tdd
  4. Activity name: TemperatureConverterActivity
  5. Application name: Temperature Converter
  6. Finish


Add testing infrastructure using JUnit

We are using a different folder to keep our tests in order to permit a clear separation in case the test should be removed at production.

  1. Select TDD Android project node in Packages
  2. Right click and select New Source Folder
  3. Folder name:test
  4. Finish

Then

  1. Select the newly created test folder
  2. Select New Java Package
  3. Name: com.example.tdd
  4. Finish


Generate test classes (JUnit version)


  1. Right click in the TemperatureConverter class
  2. Select New JUnit Test Case
  3. Select JUnit 3.x
  4. Source folder: TDD Android/test
  5. Finish

Now we will add our tests, even though our TemperatureConverter class is not clearly defined. Adding the tests will help us define our TemperatureConverter class.

Define the tests

Accordingly with our use case in Temperature Converter we can define 2 tests

  • testCelsiusToFahrenheit
  • testInvalidCelsiusTemperature

to verify our tests' resluts we will use some data obtained from an external converter and we will put these figures in a conversion table. Some temperature measure background can be obtained from http://en.wikipedia.org/wiki/Temperature and an online converter can be found at http://www.onlineconversion.com/temperature.htm. We will add a conversionTable to verify our tests shortly.

Edit the source file and:

  1. add public void testCelsiusToFahrenheit()
  2. add public void testInvalidCelsiusTemperature()


And use this code snippet

 public void testCelsiusToFahrenheit() {
fail("Not implemented yet");
}

public void testInvalidCelsiusTemperature() {
fail("Not implemented yet");

Adding the conversion table

Also in the TemperatureConversionTest class

  1. Add an attribute private static final Map<Integer, Integer> conversionTable
  2. Set its default value to new HashMap<Integer, Integer>()
  3. Shift + Ctrl + O to resolve the imports

Initialization code

  1. Initialize the conversion table to
static { // initialize (c, f) pairs conversionTable.put(0, 32); conversionTable.put(100, 212); conversionTable.put(-1, 30); conversionTable.put(-100, -148); conversionTable.put(32, 90); conversionTable.put(-40, -40); conversionTable.put(-273, -459); }

just after its definition

Run the tests

Right click on the TDD Android project node and select Run as JUnit Test We will se how, as expected. our tests fail.

Notice that these tests will run outside the Android emulator. Later we will include these tests to be run by the emulator itself.

Complete the tests


Now, once our test infrastructure is setup, let's proceed to define our tests.

  1. Add
public void testCelsiusToFahrenheit () 
for (int c: conversionTable.keySet()) {
int f = conversionTable.get(c);
String msg = "" + c + "C -> " + f + "F";
assertEquals(msg, f, TemperatureConverter.celsiusToFahrenheit(c));
}
}
  1. Add
   public void invalidCelsiusTemperature () {
try {
int f = TemperatureConverter.celsiusToFahrenheit(-274);
} catch (RuntimeException ex) {
if (ex.getMessage().contains("below absolute zero")) {
return;
}
else {
fail("Undetected temperature below absolute zero: " + ex.getMessage());
}

}

fail("Undetected temperature below absolute zero: no exception generated");

}
  1. If some imports are missing just press Shift + Ctrl + O

Creating the real code

We have now the tests for code that still doesn't exist. If everything was fine, you should see a light bulb in the line where TemperatureConverter.celsiusToFahrenheit is invoked and giving you the alternative to

Create Method celsiusToFarenheit(int) in tdd.TemperatureConverter 
  1. Create the method public static int celsiusToFahrenheit(int celsius) in TemperatureConverter (Notice the int return type and the celsius parameter)
  2. Return any value, say 0, or raise an exception indicating that it's not implemented yet.

Running the tests

Our Test Driven Development is starting to appear.

Go to the JUnit tab and Rerun Tests

These tests will fail, because we haven't already implemented the celsiusToFahrenheit conversion method.

We obtain two failures but by very different reasons as before:

  1. testCelsiusToFahrenheit failed because a wrong conversion result was obtained (remember that we returned a constant 0)
  2. testInvalidCelsiusTemperature failed because no exception was generated for an invalid temperature

So, what's left is to go there an implement it.

celsiusToFahrenheit

Replace this

return 0

by

return (int)Math.round(celsius * 1.8 + 32);

Remember to change parameters name to celsius (from c) if it's not already named.

Running the tests again, and we will see how one test succeed but the other fails with the message

Undetected temperature below absolute zero: no exception generated

That's because we are expecting an exception to be thrown if the temperature is below the absolute zero but our first attempt doesn't do that.

Let's add this condition, but we first need the ABSOLUTE_ZERO_C constant.

Add ABSOLUTE_ZERO_C constant

TemperatureConverter class editor window.

  1. Add
    public static final int ABSOLUTE_ZERO_C = -273;

Modify celsiusToFahrenheit

Add the absolute zero check
  public static int celsiusToFahrenheit(int celsius) {
if (celsius < ABSOLUTE_ZERO_C) {
throw new RuntimeException("Invalid temperature: " + celsius + " below absolute zero");
}
return (int)Math.round(celsius * 1.8 + 32);

}

Run the tests

Run the tests again and we can verify that in our third attempt both tests passed.

Functional tests

We now have our TemperatureConverter class tested and ready to be included in our Android project.

Create the layout

Select main.xml in layout and add
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout id="@+id/linear" android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
<TextView id="@+id/message"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="6dip"
android:text="Enter the temperature and press Convert">
</TextView>
<TableLayout id="@+id/table" android:layout_width="fill_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:stretchColumns="1">
<TableRow>
<TextView id="@+id/celsius_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dip"
android:textStyle="bold"
android:textAlign="end"
android:text="Celsius">
</TextView>
<EditText id="@+id/celsius"
android:padding="3dip"
android:numeric="true"
android:digits="-+0123456789"
android:textAlign="end"
android:singleLine="true"
android:scrollHorizontally="true"
android:nextFocusDown="@+id/convert"
>
</EditText>
</TableRow>
<TableRow>
<TextView id="@+id/fahrenheit_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dip"
android:textStyle="bold"
android:textAlign="end"
android:text="Fahrenheit">
</TextView>
<EditText id="@+id/fahrenheit"
android:padding="3dip"
android:textAlign="end"
android:singleLine="true"
android:scrollHorizontally="true">
</EditText>
</TableRow>
</TableLayout>
<Button id="@+id/convert" android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="6dip"
android:text="Convert"
android:textAlign="center"
android:layout_gravity="center_horizontal"
android:nextFocusUp="@+id/celsius"
>
</Button>
</LinearLayout>

There's a lot of documentation and tutorials online in case you need to know more about Android layouts. We now have an empty form.

Change the theme, if you want, in AndroidManifest.xml, adding

android:theme="@android:style/Theme.Dark"

to <application>

We will obtain this when we run as Android Application

Of course this is not functional yet.

Positron

provides an instrumentation and some support classes to help writing acceptance tests. It is provided as a jar that gets bundled with your application. Acceptance tests are written in junit, extending a custom base class. Positron can be downloaded from http://code.google.com/p/android-positron/downloads/list.

Positron jar must be added to build path. In this example we are using positron-0.4-alpha, but if by the time you read this there's a newer version you may use it.

Validation

Create com.example.tdd.validation package in test folder

Create OverallTest test case

In the newly created package com.example.tdd.validation create a new JUnit test case

  1. Source folder: TDD Android/test
  2. Package: com.example.tdd.validation
  3. Name: OverallTest
  4. Superclass: positron.TestCase
  5. Finish

Create Positron class

In the package com.example.tdd create the Positron class extending positron.Positron

  1. Source folder: TDD Android/test
  2. Package: com.example.tdd
  3. Name: Positron
  4. Superclass: positron.Positron
  5. Finish

Then, create our test suite including OverallTest tests. Other tests can also be added.

 @Override
protected TestSuite suite() {
TestSuite suite = new TestSuite();
suite.addTestSuite(OverallTest.class);
return suite;
}

Create the test


In OverallTest create the testConversion test case

 public void testConversion() {
Intent intent = new Intent(getTargetContext(), TemperatureConverterActivity.class);
startActivity(intent.addLaunchFlags(Intent.NEW_TASK_LAUNCH));

TemperatureConverterActivity activity = (TemperatureConverterActivity)activity();

// Is it our application ?
assertEquals(getTargetContext().getString(R.string.app_name), activity().getTitle());

// Do we have focus ?
assertEquals(activity.getCelsiusEditText(), activity.getCurrentFocus());

// Enter a temperature
sendString("123");

// Convert
press(DOWN, CENTER);

// Verify correct conversion 123C -> 253F
assertEquals("253", activity.getFahrenheitEditText().getText().toString());

}

This basically represents what we defines in our Use Case: Convert Temperature:

Actor Action System Response
1. The user enters a temperature in Celsius, and then press Convert 2. The system converts the temperature to Fahrenheit and the result is presented to the user
3. The user wants to enter another temperature and continues from 1 or presses Back to exit. 4. The process finishes.
Alternative Courses
Temperature is below absolute zero Indicate error
Invalid input characters entered Indicate error

Finally, add this to tearDown

 protected void tearDown() throws Exception {
finishAll();
}

Instrumentation

Add the instrumentation definition to AndroidManifest.xml
<instrumentation class=".Positron" android:functionalTest="true" android:targetPackage="com.example.tdd" android:label="Temperature Converter Acceptance Tests"/>


Complete TemperatureConverterActivity

In order to compile these tests we have to add the fileds related with the views widget in the GUI
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

// Find text fields.
celsiusEditText = (EditText)findViewById(R.id.celsius);
fahrenheitEditText = (EditText)findViewById(R.id.fahrenheit);
convertButton = (Button)findViewById(R.id.convert);

// disable fahrenheit field
fahrenheitEditText.setEnabled(false);

}

Also add the fields proposed by the IDE

private EditText celsiusEditText;;
private EditText fahrenheitEditText;
private Button convertButton;

and using Source->Generate Getters and Setters... generate the corresponding getters.

Run the tests again

This time let's run the positron tests in the emulator. To achieve this

adb shell "/system/bin/am instrument -w com.example.tdd/.Positron"

and change to the DDMS inside Eclipse, and look for the LogCat window

I/Positron(1091): .F
I/Positron(1091): Time: 1.332
I/Positron(1091): There was 1 failure:
I/Positron(1091): 1) testConversion(com.example.tdd.validation.OverallTest)junit.framework.ComparisonFailure: expected:<253> but was:<>
I/Positron(1091): at com.example.tdd.validation.OverallTest.testConversion(OverallTest.java:62)
I/Positron(1091): at java.lang.reflect.Method.invokeNative(Native Method)
I/Positron(1091): at positron.harness.InstrumentedTestResult.run(InstrumentedTestResult.java:37)
I/Positron(1091): at junit.extensions.TestDecorator.basicRun(TestDecorator.java:22)
I/Positron(1091): at junit.extensions.TestSetup$1.protect(TestSetup.java:19)
I/Positron(1091): at junit.extensions.TestSetup.run(TestSetup.java:23)
I/Positron(1091): at positron.Positron.onStart(Positron.java:62)
I/Positron(1091): at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1115)
I/Positron(1091): FAILURES!!!
I/Positron(1091): Tests run: 1, Failures: 1, Errors: 0

Again, as expected the test failed because the conversion functionality is not yet implemented.

Implementing conversion functionality in TemperatureConverterActivity

Add OnClickListener to convert button


It's an interface definition for a callback to be invoked when a view, a button in this case, is clicked. We need to implement the onClick abstract method, which in our case will call the convert helper method to carry away the actual conversion.

        // Hook up button presses to the appropriate event handler.
convertButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
convert();
}
});

Add convert method

Helper method to get the temperature value entered into the Celsius text field, convert it into Integer and call celsiusToFahrenheit method in TemperatureConverter class.

   protected void convert() {
try {
int f = TemperatureConverter.celsiusToFahrenheit(Integer.parseInt(celsiusEditText.getText().toString()));
fahrenheitEditText.setText(String.valueOf(f));
}
catch (Exception ex) {
fahrenheitEditText.setText(ex.toString());
}

}

Run tests again

Now, when we run our tests again, we obtain
I/Positron(1334): Time: 1.368
I/Positron(1334): OK (1 test)

Let's convert our unit test into Positron

In the previous unit test TemperatureConverterTest let's change the base class into positron.TestCase. You may also have to comment out the generated constructor because (until now) there's no positron.TestCase constructor receiving a String.

Run tests again

Running the tests again now we can see all of the tests are run
I/Positron(1430): Time: 2.408
I/Positron(1430): OK (4 tests)

Monday, January 21, 2008

Setting emulator time in Android


Setting the emulator time in android is fairly easy using the date command inside the android emulator image. If you are developing and testing a time related application you may found useful to change system time to force some events to happen, like an appointment alarm.

To do this, just use

$ adb shell date secs


where secs is seconds since 1970-01-01 00:00:00 UTC.

Emulator time is updated every minute, so you have to wait until the display is update, however system time has already been changed.

If you are in Linux and want to obtain secs for an arbitrary date


$ date --date="2008-01-31 17:46:59" +%s


or as a one-liner

$ adb shell date $(date --date="2008-01-31 17:46:59" +%s)


notice that we are using two different date commands, one from the android platform and the other from our Linux system. If you are using Windows or OSX, I'm sure you can find a similar way of doing this.

There are other alternatives, like installing Busybox in the system image, but this is left for another article. Stay tuned.

Thursday, January 03, 2008

Test Driven Development and GUI Testing: Functional tests

Previous article:Test Driven Development and GUI Testing: Unit tests

Google Docs: This article can be viewed at Test Driven Development and GUI Testing: Functional tests

In the previous article Test Driven Development and GUI Testing: Unit tests, we followed the Test Driven Development approach to go from our Use Case:Convert Temprature to our TemperatureConverter class which is now fully tested and will be the foundation of our application.
We now trust it, because it has passed our tests. Furthermore, these tests give us the confidence to refactor, improve and change it.
This article is about building functional tests as we implement our swing GUI.

Adding functional tests

Functional tests, also know as Acceptance tests, are other fundamental concept in eXtreme Programming. Those tests would help us assure that our application correctly implements what we described in Use Case:Convert Temprature and are always written from a client or user perspective.
Writing functional tests to validate application GUI using Test Driven Development techniques is even more trickier at the beginning.
How can you write a functional test to test a GUI that still doesn't exist ?
Functional testing is approached much like unit testing as we seen before.
Well, let's see how.

Firstly, we are adding the first component of the application GUI.

Create JFrame

  1. Select the TDD project node
  2. Right click and add new JFrame From...
  3. Name it TemperatureConverterGUI
  4. Select Source Package location and tdd package
  5. OK
We now have an empty form.

Synchronize the model

  1. In the TemperatureConverterGUI source code editor, right click
  2. Select ReverseEngineer...
  3. Existing UML: TDD-UML
  4. OK

Create test

  1. Select Functional Test Packages node
  2. Right click and add New File... | Other | JUnit | NbTestCase Test New File.. | Testing Tool | JellyTestCase Test
  3. Select Next
  4. FileName: OverallTest
  5. Folder test/qa-functional/src/validation
  6. Finish

Add libraries

  1. Add Jellytools, Jemmy and NBUnit to Libraries

Synchronize the model

  1. Right click on the OverallTest editor
  2. Select Reverse Engineer...
  3. Existing UML: TDD-UML
  4. OK
  5. Add OverallTest class from Model to TDD Class Diagram

Finishing all of these steps, we have this TDD Class Diagram

Add Jelly functional tests

Jelly is NetBeans dependent, so if you would like to avoid this you can base your tests directly on Jemmy.

In our TDD Class Diagram
  1. In OverallTest class
  2. Rename placeholder test1 to testCorrectConversion
  3. Rename placeholder test2 to testIncorrectConversion
  4. Add attribute app type ClassReference and default value initializeApp()
  5. Add method initializeApp() returning ClassReference and being private and static
  6. Generate Code...
  7. OK

Let's add this code to initializeApp() method
   private static ClassReference initializeApp () {
try {
return new ClassReference("tdd.TemperatureConverterGUI");
} catch (ClassNotFoundException ex) {
throw new RuntimeException("Couldn't initialize app", ex);
}
}
Then press Ctrl + Shift + I to fix the imports.

Mark tests as not yet implemented

Stub tests, as generated by NetBeans New | JellyTestCase Test are empty, so we need to add these sentences temporarily
    public void testCorrectConversion () {
fail("This test is not yet implemented");
}

public void testIncorrectConversion () {
fail("This test is not yet implemented");
}

Add the tests to the suite

These tests should be added to the existing suite. This is not automatically changed when we changed the tests names in the TDD Class diagram.
    public static NbTestSuite suite () throws ClassNotFoundException {
NbTestSuite suite = new NbTestSuite();
suite.addTest(new OverallTest("testCorrectConversion"));
suite.addTest(new OverallTest("testIncorrectConversion"));
return suite;
}

Modify build-qa-functional.xml

To be able to compile and run Jelly test cases we have to modify the rules in build-qa-functional.xml

<!-- Path to Jemmy library -->
<path id="jemmy.path" location="/opt/java/netbeans-6.0/testtools/modules/ext/jemmy.jar">

<!-- Path to Jelly library -->
<path id="jelly.path" location="/opt/java/netbeans-6.0/testtools/modules/ext/jelly2-nb.jar">

<!-- ========= -->
<!-- Compilers -->
<!-- ========= -->

<!-- Compile functional tests. This target is used in cfg-qa-functional.xml. -->
<target name="qa-functional-compiler">

<!-- Build application before tests -->
<ant dir=".." target="jar">
<buildTests srcdir="qa-functional/src" compileexcludes="**/data/**">
<classpath>
<!-- Add classpath elements needed to compile tests -->
<path refid="jemmy.path">
<path refid="jelly.path">
<fileset dir="../dist" includes="*.jar"> </fileset>
</path>
</path>

<!-- ========= -->
<!-- Executors -->
<!-- ========= -->
<!-- Run tests in JVM -->
<target name="run-jvm">
<executeTests pluginname="jvm">
<classpath>
<!-- Add classpath elements needed to run tests -->
<path refid="jemmy.path">
<path refid="jelly.path">
<fileset dir="../dist" includes="*.jar"> </fileset>
</path>
</path>

Run the tests

As expected recently added tests will fail because we forced the fail condition until we implement them.

Review Temperature Converter mock-up

In this mock-up we can identify the widgets required by the application.

We need
  • a window having "Temperature Converter" title
  • two text fields, one editable to enter the temperature and the other not editable to show the conversion result
  • two labels corresponding to each text field showing the corresponding temperature units
  • one convert button to do the conversion
  • one close button to close the windows and exit the application
That's how our application will look like or its structure.

From the behavioral point of view, we can say that every time the user presses the Convert button, a conversion is carried away and the result showed. If there's a problem with the conversion the error is also showed in the conversion text field but to get user attention this is showed in red, for example:

Invalid temperature: -274 below absolute zero

Now that we have identified the required components we can proceed to write the tests.
Yes, we haven't written the application yet but using the knowledge we obtained analyzing this mock-up we are going to write our tests expecting the components to be there.

Implementing the tests

We are using Jelly/Jemmy to implement our swing GUI tests. Jelly/Jemmy use the concept of Operators.

Add Jemmy Operators

Jemmy operators is a set of classes which are test-side agents for application components. Operators provide all possible methods simulating user action with components, methods to find and wait components and windows. Also operators map all components methods through the event queue used for event dispatching. All Jelly operators are subclasses of Jemmy operators.

All of the operators provide access to their subcomponents by "getters" methods. These methods are implemented using the "lazy initialization" technique, so real suboperator instances are not initialized until it's necessary. All of the suboperators are initialized by verify() method invocation, so this method guarantees that all subcomponents are already loaded.


So, our first step is to add such Operators.
We have to add these attributes to our OverallTest class in the TDD Class Diagram
  • jfo type JFrameOperator
  • jlfo and jlco type JLabelOperator
  • jtffo and jtfco type JTextFieldOperator
  • jbcvo and jbclo type JButtonOperator

Add a String title attribute with default value "Temperature Converter" to keep window title.

Initialize Operators in findOperators

Let's add the private findOperator method.
Then
  1. Right click on the OverallTest class and select Generate Code...
  2. Select TDD as target project
  3. Select Functional Test Packages as Source Root
  4. Check Add Merge Markers to Existing Source Elements
  5. OK
When finished, select findOperators method, right click on it and Navigate To Source.
Add this code to the method
   private void findOperators () {
//wait frame
jfo = new JFrameOperator(title);
jlco = new JLabelOperator(jfo, "Celsius");
//
// Using the getLabelFor is a way to locate JTextFields, the other is by name or by initial text
//
jtfco = new JTextFieldOperator((JTextField) jlco.getLabelFor());
jlfo = new JLabelOperator(jfo, "Fahrenheit");
jtffo = new JTextFieldOperator((JTextField) jlfo.getLabelFor());
jbcvo = new JButtonOperator(jfo, "Convert");
jbclo = new JButtonOperator(jfo, "Close");
}

Write the actual tests

testCorrectConversion tests some conversions that are know to be correct, and as a double check the actual result is compared against TemperatureConverter.celsiusToFahrenheit() which has passed the unit tests.
After entering the text into the field, the Convert button is pressed and the value in the Fahrenheit text field is checked.

    public void testCorrectConversion () {
int[] temps = {100, -100, 0, -1, 1};

for (int t : temps) {
jtfco.setText(Integer.toString(t));
jbcvo.clickMouse();

// verify the result
int fahrenheit = TemperatureConverter.celsiusToFahrenheit(t);

assertEquals("conversion", "" + fahrenheit, jtffo.getText());
assertEquals(Color.BLACK, jtffo.getForeground());
}
}
testIncorrectConversion invokes the conversion with malformed or invalid parameters.
    public void testIncorrectConversion () {
String[] temps = { "aaa", "0-0", "--1", "1a", "-274" };

for (String s : temps) {
jtfco.setText(s);
jbcvo.clickMouse();

assertEquals(Color.RED, jtffo.getForeground());
}
}

Application start

Add this code to start the application and to find Jemmy operators. If you add the method calls and then you can use the IDE to complete the surrounding try-catch block.
    public void setUp () {
try {
System.out.println("######## " + getName() + " #######");
app.startApplication();
findOperators();
} catch (InvocationTargetException ex) {
Logger.getLogger(OverallTest.class.getName()).log(Level.SEVERE, null, ex);
} catch (NoSuchMethodException ex) {
Logger.getLogger(OverallTest.class.getName()).log(Level.SEVERE, null, ex);
}
}

Running the tests

Finally, our functional test infrastructure is ready. To sum up: we have now two tests that using the swing GUI, that we are going to implement right now, are converting temperatures and verifying the results and obtaining corresponding error messages when the input data is incorrect.
This was defined in our Use Case:Convert Temprature.
We can see an empty window, and after a while the browser is launched and both tests failing with

fail: Frame with title: "Temperature Converter"

Clicking on the Yes link under Workdir a screenshot is saved and can be analyzed to solve the problem.
That's because we need to implement our swing GUI.

UML TDD Class Diagram

This will be our final Class Diagram, after we implement the GUI

Next article

Next article Test Driven Development and GUI Testing: Implementing swing GUI (coming soon) will show how to build the swing based GUI using Test Driven Development approach and we will obtain our working and fully tested application.

Comercial de Vodafone filmado en Buenos Aires

Este es otro comercial filmado en Buenos Aires, esta vez el anunciante es Vodafone.