Monday, January 2, 2012

Android unit testing with test database

In order to make your tests use a different database to your live one, have your test class extend ProviderTestCase2:

    public class MyProviderTest  extends ProviderTestCase2

Override two constructors:
    public MyProviderTest()
    {
        this(MyContentProvider.class, "my_authority_from_manifest");
    }


    public MyProviderTest(Class providerClass, String providerAuthority)
    {
        super(providerClass, providerAuthority);
    }


What this does is, in the setup() method, creates a IsolatedContext instead of the usual Context. Additionally, the base Context of this IsolatedCopntext is a RenamingDelegateContext. This class contains a field to store a prefix for the name of your database. It defauls to "test.".


In your tests, wherever you need a context object, call getMockContext(). This will return the IsolatedContext.

Sunday, January 1, 2012

Unit Testing Android When ContentProvider is Used

I've been having 'fun' and games with unit tests and ContentProviders this week :-( I thought it would be good practise to take an app which directly accesses a db for it's data, and to instead implement a ContentProvider and access the data that way. Touchy feely android and all that.

The tutorial I used was here and it all seemed fairly straight forward, although authority/uris were new to me.

My problems started when I came to changing my 'unit' tests (quoted as REAL unit tests wouldn't touch the db, of course). I had lots of failing tests! I picked one out to concentrate on:

    public void testAddNewVehicle()
{
Vehicle vehicle1 = new Vehicle("xyz", "my car", 111f);
long result = VehicleProvider.addVehicle(getContext(), vehicle1);

assertTrue(1 == result);

ArrayList vehicles = VehicleProvider.getVehicles(getContext());

assertEquals(1, vehicles.size());
assertEquals("xyz", vehicles.get(0).getRegistrationNo());
assertEquals("my car", vehicles.get(0).getDescription());
assertEquals(111.0f, vehicles.get(0).getInitialMileage());
}

So, it creates then adds a vehicle to the db, then retrieves the vehicle(s) from the db and asserts against it. This test failed at the:
    assertEquals(1, vehicles.size());
line. Hmmm... so the insert worked, but the retrieval didn't. Nice. Even nicer, the test passed in 'run' mode (as opposed to debug, in eclipse). Joy.

I ended up opening a command prompt at the database directory of the emulator. What I found was the database didn't exist on the file system after the insertion of the vehicle, so although the insertion was successful, when getVehicles() came along and retrieved data from the db, it didn't exist. As part of the getVehicles(), getWriteableDatabase() is called which creates the database if it does not exist. So getVehicles() would create a new db and of course it was empty, and the vehicles.size() was 0, not 1.

A couple of weeks ago I played about with viewing the Android source in eclipse, so I thought I'd make use of that ability here. I downloaded the 1.6 source (the app was running on 1.6) and stepped through my test. What I found (with the help of logging in onCreate() of the ContentProvider*) was that when the test is run, something extra happens; ContentProvider.onCreate() is called before the test begins. This is perhaps part of the testAndroidTestCaseSetupProperly() test that automatically gets added to test cases in Android.

This seemed to partially initialise the db. What this means is that the first time I try to access the db, Android thinks the db is already up and available and doesn't go through the full initialisation process. This is an excerpt from the Android source for getWriteableDatabase() in SQLiteOpenHelper:

    public synchronized SQLiteDatabase getWritableDatabase() {
        if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
            return mDatabase;  // The database is already open for business
        }
        //lots more initialisation here
    }


What was happening in the if statement was that mDatabase was not null and was open. So Android was saying, there you go, here's your database.

But why the difference in 'run' mode. The logging in the onCreate() of the ContentProvider showed that it was being executed once pre-test, once during the test and one other time between these two! I don't know what was going here or why this 'hid' the issue I'd found in debug mode, I was just happy to be making progress in fixing things.

Here's the logcat from debug:

01-01 20:29:49.024: D/UKMPG(1554): onCreate in ContentProvider
01-01 20:29:51.565: D/UKMPG(1572): onCreate in ContentProvider

and from run:
01-01 20:30:37.505: D/UKMPG(1612): onCreate in ContentProvider
01-01 20:30:39.884: D/UKMPG(1631): onCreate in ContentProvider
01-01 20:30:39.904: D/UKMPG(1631): onCreate in ContentProvider

It's not displayed here, but the very first entry for both run and debug does not contain my application package name, which I assume means it was not run by my app, but by the testAndroidTestCaseSetupProperly() Android method. Notice it also is executed in a different thread. So for some reason when using 'run' to run the test, onCreate is executed twice by my app...


I did manage to fix the test by making sure the db was closed before exiting the onCreate in the ContentProvider, something they didn't do in the tutorial. That's my excuse!

Happily, things work as expected in run and debug modes for the application itself - just a single call to onCreate int the ContentProvider :-)

My next task is to work out how to use the Android mock classes to for the tests to use a test db.

* It's not possible to hit break points in this pre-test run

Some related Stackoverflow questions:
http://stackoverflow.com/questions/8679041/android-unit-test-fails-in-debug-passes-otherwise
http://stackoverflow.com/questions/8688252/what-does-testandroidtestcasesetupproperly-do

NOTE: out of interest I just had a look at the source for the testAndroidTestCaseSetupProperly() method. All it does is check that the Context object is not null:

public void testAndroidTestCaseSetupProperly() {
        assertNotNull("Context is null. setContext should be called before tests are run",
                mContext);        
    }

Sunday, October 2, 2011

Android Asset Folder

There seems to be a limit to the size of file you can read from if it placed in the assets folder in Android. Trying to read bytes from a file over 1MB results in an IOException.

Stackoverflow suggests splitting files into 1MB chunks (or giving the file an extension such as .mp3 - i.e. already compressed), and that the problem is due to Android compressing files in the assets folder when they're above a certain size.

Saturday, October 1, 2011

Ignoring Case in SQLite Android Queries

Instead of using the "=?" string to match strings exactly, use "like?":

    db.query(TABLE_NAME, COLUMNS, REG_COLUMN + " LIKE? ", new String[] {searchQuery}, null, null, null);

instead of:

    db.query(TABLE_NAME, COLUMNS, REG_COLUMN + "=? ", new String[] {searchQuery}, null, null, null);




Saturday, September 17, 2011

Testing internal C# methods in Visual Studio Express

Just add the following to AssemblyInfo.cs in the project under test:

   [assembly: InternalsVisibleTo("NameOfTestProject")]

Saturday, September 10, 2011

Building Android with Ant and External Libraries

Android seems set up by default to look in a folder called libs for libraries, so just drop your jars in there and it should just work!

Friday, September 2, 2011

Android Process Monitor

I have an app which needs to monitor whether another app is running or not. I used the following code:

    List runningAppProcessInfos =
((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getRunningAppProcesses();

and search the List of RunningAppProcessInfos for the process in question, after filtering out the running services. It works perfectly, except when a search for the process in question coincides with plugging the device into a usb charger (laptop in this case). For some reason, this code fails; the process I am interested in never stops running according to logcat, but it seems to be missing from the List as my app doesn't find it, so exits (part of the logic, not a crash).

Oh well, one to look out for...

Saturday, August 27, 2011

Copying files in Java


This is something I've never really done much of, but when I've seen snippets of code which do this I've always thought how ugly and cumbersome it looks:

    File srcFile = new File(sourceFilePath);
    File destFile = new File(destinationFilePath);
    FileInputStream in = new FileInputStream(srcFile);
    FileOutputStream out = new FileOutputStream(destFile);
    byte[] buf = new byte[2048];
    int len;
    while ((len = in.read(buf)) > 0)
    {
        try
        {
   out.write(buf, 0, len);
}
catch (Exception e)
{
}
    }
    out.flush();
    in.close();
    out.close();

etc...

But there is now, at last, an easier way:

    import java.nio.channels.FileChannel;
    ...
    File srcFile = new File(sourceFilePath);
    File destFile = new File(destinationFilePath);
    FileInputStream in = new FileInputStream(srcFile);
    FileOutputStream out = new FileOutputStream(destFile);
    FileChannel srcFileChannel = in.getChannel();
    FileChannel destFileChannel = out.getChannel();

    destFileChannel.transferFrom(srcFileChannel, 0, srcFileChannel.size());
    srcFileChannel.close();
    destFileChannel.close();
   ...

Sunday, August 7, 2011

Building Android with Ant

To create a build using an ant build.xml script, you'll need a local.properties file. This point to your sdk installation directory.

You can create this with the android tool like so:
    android update project --path .

Execute from your project's directory. The full stop is important!

Remember you'll need to add keystore and proguard if you want to sign/obfustcate etc:
    key.store=/path_to_keystore/keystore
    key.alias=key_store_alias
    proguard.dir=/path_to_proguard/lib (seems to use lib dir rather than bin)

(See here for more about signing and obfuscation)

Saturday, July 23, 2011

Debugging with Nunit and Visual Studio Express

It's possible to set breakpoints in unit tests in the freebie versions of MS Visual Studio:
  1. add a reference to the C:\Program Files\NUnit 2.5.10\bin\net-2.0\lib\nunit-console-runner.dll to your test project
  2. add the following class to your test project
  3. using System;
    namespace MyTests
    {
        class NUnitDebugRunner
        {
            [STAThread]
            static void Main(string[] args)
            {
                NUnit.ConsoleRunner.Runner.Main(args);
            }
        }
    }

  4. in the test project properties, under 'Application' set the output type as 'Windows Application and the Startup objects as the class created in the previous step. Under the debug tab, browse to the folder containing the csproj for the test project in the 'Working directory' field and put the name of the csproj file in the C'Command line arguments' field
  5. set the test project as the startup project
Now when you hit F5, any breakpoints in the tests will be hit.

Thanks to Blokley

Wednesday, July 20, 2011

Executing a script at startup

Having just reinstalled Linux Mint, I needed to install and setup Twonky media server again. Installing was easy with the supplied script, but to start the server (and kill it on shutdown - don't know if this is strictly necessary) you have to create a symbolic link in /etc/rc2.d which points to the twonky script in /etc/init.d and give it a name starting with Snn, where nn is a 2 digit number from 01 to 99:
    sudo ln -s /etc/init.d/twonkyserver /etc/rc2.dS99twonky


To kill the server, do the same in /etc/rc0.d but call the symbolic link Knn:
    sudo ln -s /etc/init.d/twonkyserver /etc/rc0.d/K99twonky

Monday, July 18, 2011

Dual monitor setup on Linux Mint

I had a dual monitor setup running perfectly on Linux Mint 10, but struggled to get it going under Mint 11. From Googling it appears I was not alone, so I decided to skip 11 and revert to 10.

These are the steps for a clean Mint 10 installation:

  1. open the additional drivers application and install the recommended drivers and restart the machine
  2. open the NVIDIA settings and enable the second monitor and check the Xinemara box
  3. restart the machine
That's it!

Sunday, July 3, 2011

Forcing screen to stay on

Here's a nice way to make the screen stay on for a particular activity. Just put this in the onCreate method:

    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);


It's nicer than the POWERLOCK method, which requires an additional permission in the manifest:

    <uses-permission android:name="android.permission.WAKE_LOCK">

And in your Activity:

    final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    this.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
    this.mWakeLock.acquire();

    //and when you're done with the WakeLock:
    this.mWakeLock.release();

Monday, May 30, 2011

Android strings.xml

Here's a strange one - I was working on an Android application, using the strings.xml file for the apps string resources. It so happened that I needed to have two strings in both underlined and non-underlined forms. So I added:
<string name="average_underlined"><u>average</u></string>
<string name="recent_underlined"><u>recent*</u></string>
<string name="average">average</string>
<string name="recent">recent</string>


to the strings.xml file. However, when I ran the app the underlined versions appeared where I expected the regular version to appear. After checking and rechecking I was happy the code was correct. Bizarrely, the fix was to have the non-underlined versions declared first:
<string name="average">average</string>
<string name="recent">recent*</string>
<string name="average_underlined"><u>average</u></string>
<string name="recent_underlined"><u>recent*</u></string>


If anyone can explain this to me please do get in touch!

Thursday, May 26, 2011

Android - Aligning Two Views Side-by-Side

This was harder than I thought it would be - I tried playing about with relative views plus alignment settings, table views, gravity and so on.

The answer turned out to be use a LinearLayout with orientation set to horizontal and for each view (buttons in my case) set width to fill parent and layout_weight to "1":


<LinearLayout
  android:id="@+id/Table01"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal"
android:layout_alignParentBottom="true">    
<Button
android:id="@+id/button1"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="15sp"
android:layout_width="fill_parent"
android:layout_weight="1"
android:text="reset">
</Button>
<Button
android:id="@+id/button2"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="15sp"
android:layout_width="fill_parent"
android:layout_weight="1"
android:text="delete">
</Button>
  </LinearLayout>

Monday, May 2, 2011

Android ListViews

I've been working on an Android ListActivity which needs to have two TextViews anchored to the bottom of the screen. The anchoring was fairly simple, but when the list extended to the bottom of the screen, it obscured the anchored TextViews.

The solution is to declare the ListView last in your xml layout file, and specify what you want to be above it and what you want to be below it. I.e.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android
    ....>

    <TextView
        android:id="@+id/my_screen_header"
        .......>
    </TextView>

    <RelativeLayout
        android:id="@+id/my_screen_footer"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true">
        <TextView
            android:id="@+id/first_footer_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="15sp">
        </TextView>
        <TextView
            android:id="@+id/second_footer_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/first_footer_text_view"
            android:textSize="15sp">
        </TextView>
    </RelativeLayout>
    <ListView android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_above="@id/my_screen_footer"
        android:layout_below="@id/my_screen_header"/>
</RelativeLayout>

Thanks to http://blog.maxaller.name/2010/05/attaching-a-sticky-headerfooter-to-an-android-listview/

Saturday, April 23, 2011

Pain-free iTunes

Well, relatively pain free anyway. What a horrible horrible piece of software it is too - massive, slow, unintuitive. It make me feel tense using it. I use Linux as my OS but find that I have to use iTunes to manage music for my iPhone.

A little background - I have a lot of mp3s, all from CD ripped to the Uberstandard. Basically, this standard ensures the best possible mp3 reproduction of the CD, with all mp3 files tagged correctly. When I first started ripping CDs this way, I was told to avoid letting iTunes manage mp3s as it could change the tags, meaning the mp3 no longer met the standard. I'm not certain that this is still true, but I don't want to simply point iTunes at my music folder and let it do its stuff. I'm sure for people who are happy for their mp3s to be altered, iTunes does a fine job. For me, however, using iTunes means copying tracks to a new location before iTunes gets its paws on them.

I tried dragging and dropping from the new location onto the iTunes UI, but iTunes created duplicates of every track. Useless...

Anyway, because I use iTunes so infrequently (occasionally stick a load of tracks on the phone, get annoyed, and get the hell out of there) I've never really worked out the steps to follow each time that will work for me. I have now, and here they are:


  1. find the iTunes media folder. For me this is C:\My Music\iTunes\iTunes Media
  2. in there there is a folder called "Automatically Add to iTunes"
  3. choose the music you want to put on the phone/iPod and copy it to a new location
  4. from this new location, drag and drop the music files to the "Automatically Add to iTunes" folder
  5. if iTunes is running, it will automatically move the mp3s to the library. For me, it leave empty directories behind, but I can handle that
  6. now, back in iTunes, select the device you want the music added to. Select Music at the top of the screen, select Sync Music and select the "Selected playlists, artists and genres" option. This means on the artists/albums etc you choose lower down on the screen will be added to the device. Anything that is already on the phone but is not checked on this screen will be removed from the device. I think it's this bit that takes some getting used to
  7. click Apply

Friday, April 22, 2011

Obfuscating Android

Had a play around with Proguard today. It's an obfuscation tool. Instructions for setting it up for Android are here.

However, I received an error when running
    sudo ant release -v
 The error was:
    Expecting class path separator ':' before '{' in argument number 1

I found the answer - I just needed to add "external.libs.dir=libs" to the default.properties file.

Thanks to Android Bridge

Friday, February 4, 2011

Rsync

Decided I'd have a go at using rsync to do my backups. I played around with a few switches, but settled upon:
    -rltpDiv

I tried
    -aiv
but this attempts to preserve owner/group of files. This is no good for me, as the NSLU2 changes these attributes. I'm not bothered about this at the moment, I'm just interested in getting a backup to run which does transfer 10s of GB each time when all that's changed is file ownership.

So the full command to back up my home directory is:
    rsync -rltpiv /home/barry /media/nslu2/backup/rsync_home_backup --exclude '.gvfs' --exclude 'Dropbox'
where
    -r means recursive
    -l means copy symlinks as symlinks
    -t means preserve modification times
    -p means preserve permissions
    -i means display differences between source and destination
    -v means verbose output