Wednesday, May 26, 2010

ABCs - Abstract Base Classes

What's ABCs?
The abc (abstract base class) module was introduced in version 2.6 of Python. The module provides the infrastructure for defining an abstract base class and can typically be used in situations where frameworks need to specify a bunch of interfaces to be implemented to ensure the functionality. An example can be a database application where the framework requires you to provide a Data Access Object with basic CRUD operations and therefore defines an interface, call it UserDao, with the following methods:

UserDao

  • getById - Return a user entity with the specified id
  • store - store a user entity
  • remove - remove a user entity from the persistent storage
To ensure that developers follows the protocol an abc is introduced.

The UserDao abc
The following example will show you basic usage of the abc module. You should read the module documentation for more details.

Let's create the UserDao abc:
from abc import ABCMeta, abstractmethod

class UserDao:
    """
    This interface should be implemented to provide the framework with
    basic user CRUD operations.
    """
    __metaclass__ = ABCMeta

    @abstractmethod
    def getById(self, userId):
        """ Return the user with the user id. """

    @abstractmethod
    def store(self, user):
        """ Persist the user. """

    @abstractmethod
    def remove(self, user):
        """ Remove the user from the persistent storage. """
If we try to create an instance of this class we get the following error:
>>> dao = UserDao()
Traceback (most recent call last):
  File "", line 1, in 
TypeError: Can't instantiate abstract class UserDao with abstract methods getById, remove, store
In other words, we have a well defined interface to support CRUD operations for User entities and we also have an easy to read documentation of what's expected from a UserDao. If we would rely on duck typing instead of defining the abc, we would need to document the expected behavior for the UserDao elsewhere. I think that the biggest advantage of using ABCs is that you have the interface definition and documentation in one place.

Just to prevent flaming :) I enjoy duck typing and use it a lot but I think that ABCs are useful is some situations.

Implementing the abc
Implementing the UserDao can be done by sub-classing the UserDao.
class User(object):
    def __init__(self):
        self.id = None
        self.name = None

class UserDaoImpl(UserDao):

    def getById(self, userId):
        user = User()
        user.id = userId
        user.name = 'A User'
        return user

    def store(self, user):
        print "Storing: ", user.id, "-", user.name

    def remove(self, user):
        print "Removing: ", user.id
And to complete the example:
userDao = UserDaoImpl()
user = userDao.getById(10)
user.name = "New Name"
userDao.store(user)
Wrap up
I haven't investigated if ABCs introduces any performance overhead but I can't really see why they should. One advantage of using ABCs in frameworks is that you can have the documentation and interface details as a simple Python class. Another advantage is that Python will issue an error if a sub-class doesn't implement all abstract methods/properties of the abc, which is probably the author's intention.

Tuesday, May 4, 2010

Network manager problems on Kubuntu 10.04

I installed 10.04 as soon as it was released and it worked right out of the box. But suddenly today the network manager refused to start.

Well, I could see the icon in the systray but it didn't show any network interfaces (I have eth0 and wlan0).

To get me an IP I had to run dhclient manually from the terminal which was successful. Finally I could google for help.

Google pointed me to this blog post where the author had run into the same problem.

Apparently there is a bug filed for this issue, I'm just too lazy to search in the bug database :)

Just wanted to share this with you if you bump into it.

Friday, April 16, 2010

Unit testing with Qt

It's very easy to do unit testing with Qt. If you have any previous experience with JUnit you shouldn't have any difficulties using QTestLib.

All that is required to create a unit test, besides adding 'QT += testlib' to your .pro file, is that the test class is a sub-class of QObject and each test function is declared as a private slot. To run all your test functions in a class simply call QTest::qExec() from the main function.

The following snippet shows what a unit test looks like:
#include <QtTest/QTest>

class MyTest : public QObject
{
Q_OBJECT
public:
    explicit MyTest(QObject *parent = 0) : QObject(parent) {}

private slots:
    /** Test function 1 */
    void test1()
    {
        QCOMPARE('a', 'a');
    }

    /** Test function 2 */
    void test2()
    {
        QVERIFY(10 != 11);
    }

};
int main(int argc, char *argv[])
{
    // Simple test cases doesn't require a
    // Q[Core]Application instance.
    MyTest t;
    QTest::qExec(&t);
}
// The next line is only required if we have the
// definition and implementation in the same file
// as in this case.
#include "qunittest.moc"
The test above produces the following output when executed:
Config: Using QTest library 4.6.2, Qt 4.6.2
PASS   : MyTest::initTestCase()
PASS   : MyTest::test1()
PASS   : MyTest::test2()
PASS   : MyTest::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped
QVERIFY evaluates the expression and continues the execution if the expression evaluates to true. If the expression evaluates to false a message is appended to the test log and the test function is stopped. QCOMPARE is similar to QVERIFY and gives a more verbose output. In addition to the test functions the framework will look for the following four functions (must also be declared as private slots):
  • initTestCase() - Executed before any test function
  • cleanupTestCase() - Executed after the last test function
  • init() - Executed before each test function
  • cleanup() - Executed after each test function
Ok, now we know the basics, lets go on and check how to do data driven tests.

Data driven tests are used when you want to exercise some piece of code with different values and is supported by the Qt testlib. To feed test data to a test function add a function with the same name as the test function appended with a '_data' suffix. So if the test function is named testFeature, the function that serves the test with data should be named testFeature_data. The test data is setup in a table. Two functions are used to create this table, addColumn and newRow. addColumn is used to define elements in the table and newRow adds data. QFETCH is used to fetch data from the table.

Lets create a simple 'capitalize' function that takes a string, capitalizes the first character and returns the result as a new string. We want to test the function with different data such as an empty string, first character is lower case and first character is upper case.
#include <QtTest/QTest>

class MyTest : public QObject
{
Q_OBJECT
public:
    explicit MyTest(QObject *parent = 0) : QObject(parent) {}

    QString capitalize(const QString &text)
    {
        QString result = text;
        result[0] = text[0].toUpper();
        return result;
    }

private slots:
    void testCapitalize_data()
    {
        QTest::addColumn("string");
        QTest::addColumn("expected");

        QTest::newRow("empty string") << "" << "";
        QTest::newRow("lower case") << "lower case" << "Lower case";
        QTest::newRow("upper case") << "Upper case" << "Upper case";
    }

    void testCapitalize()
    {
        QFETCH(QString, string);
        QFETCH(QString, expected);
        QCOMPARE(capitalize(string), expected);
    }
};
// Macro that implements main
QTEST_APPLESS_MAIN(MyTest);
#include "qunittest.moc"
Running the test gives the following output:
********* Start testing of MyTest *********
Config: Using QTest library 4.6.2, Qt 4.6.2
PASS   : MyTest::initTestCase()
QFATAL : MyTest::testCapitalize(empty string) ASSERT: "i >= 0 && i < size()" in file ../../../../opt/qtsdk-2010.02/qt/include/QtCore/qstring.h, line 690
FAIL!  : MyTest::testCapitalize(empty string) Received a fatal error.
   Loc: [Unknown file(0)]
Totals: 1 passed, 1 failed, 0 skipped
********* Finished testing of MyTest *********
Oops, forgot to check if the string is empty. We fix that by adding the following check at the beginning of the capitalize function:
if (text.isEmpty()) {
    return text;
}
Running the test again:
********* Start testing of MyTest *********
Config: Using QTest library 4.6.2, Qt 4.6.2
PASS   : MyTest::initTestCase()
PASS   : MyTest::testCapitalize()
PASS   : MyTest::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of MyTest *********
Ok, this was a quick introduction to get you started. To find out more about the test lib read the manual, api docs, and the tutorial. You'll find examples on how to test your GUI by simulating mouse clicks, key presses and how to do simple benchmarking.