Showing posts with label Unittesting. Show all posts
Showing posts with label Unittesting. Show all posts

Wednesday, September 1, 2010

Python Koans - A Great Way to Learn Python!

I just found out about Python Koans by Greg Malcolm (thanks dude) after listening to the from python import podcast podcast (which I find amusing, thanks guys).

It's an awesome way to learn Python. Instead of just reading tutorials and/or books you learn Python by coding.

The interactive tutorial is built around unit-tests and you advance and gain new skills by passing tests and it's really funny. You do learn a lot about the Python language when doing the Koans so I recommend it even if you've been using Python for a while.

Another cool thing is that you learn how to do unit testing in Python, if you're not already familiar with 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.

Wednesday, October 7, 2009

Python and unit testing

Writing unit tests in python is really easy. It's the dynamic properties of the language which makes it very simple to mock class methods, instance methods and functions. This makes the task of writing isolated unit tests easy when you practice test-driven development.

Say you have the following class:
import urllib2

class UrlDownloader(object):
    def download(self, url):
        """
        Read the content from the url.
        The content is returned as a string object.
        """
        return urllib2.urlopen(url).read()
You actually don't want to download something from the net when you do a unit test for this class. You would like to have a fixed string returned from the read() method. This can be hard to achieve in some languages (for example Java) but that's not true for Python. Python supports redefining class methods, functions and other things in run-time. The following snippet shows how easy it's to mock the urlopen() function in the urllib2 module to return a mocked file-like object:
import urllib2
import unittest

class MockFileObject(object):
    def read(self):
        return "DATA"

def mock_open(url):
    return MockFileObject()

class UrlDownloaderTest(unittest.TestCase):
    def testDownload(self):
        urllib2.urlopen = mock_open
        dl = UrlDownloader()
        self.assertEqual(dl.download(""), "DATA")

if __name__ == '__main__':
    unittest.main()
In this unit test we redefine the urlopen-function (remember, this is not a member of an instance, it's a plain function) with our own which returns a mock file-like object. It's not that hard to imagen all the possibilities this gives when writing tests. There are of course frameworks that will help you with creating mocks etc, you could try The Python Mock Module which should cover most of the use cases.

Friday, October 2, 2009

File-like objects

A Python file-like object can be seen as an abstraction to input/output streams. They are actually similar to Java's java.io.Reader/java.io.Writer abstraction (I'm coming from Java). The typical usage of file-like objects can be found in frameworks and APIs. The file-like object provides a generic way of reading/writing data and would fit an HTML-parser for example. The parser is not interested of where the data come from (files, sockets, etc), only the content, so naturally the implementation should not depend on how to open resources etc.

Basic methods that need to be implemented by a file-like object are:
  • read([size]) - read at most size bytes from the file.
  • readLine([size]) - read one entire line from the file.
  • write(str) - write a string to the file.
  • close() - close the file.
You should read the documentation of the function/method that uses the file-like object, often they specify which methods that are expected to be implemented by the file-like object. If the file-like object is only used for reading, no write-functions need to be supported. More information about file-like objects can be found here.

Ok, so why should you use file-like objects in your code?

Let's take a look at the ConfigParser class which can be used to read/write configuration data. You can expect to find methods for reading and writing configuration data using files but the class does also include support for file-like objects, which is great. For example it's very easy to write unittests for this class, thanks to the support of file-like objects,  without having to create real files with test data. To create a file-like object from a string we can use the StringIO module. The module have both read and write support.

Here's some code:
import unittest
import StringIO
import ConfigParser

class ConfigParserTest(unittest.TestCase):

    def testHasSection(self):
        """
        has_section() should return True if the section exists
        and False if is doesn't.
        """
        data = StringIO.StringIO("[My section]\nSetting=1\n")
        config = ConfigParser.ConfigParser()
        config.readfp(data)
        self.assertTrue(config.has_section("My section"))
        self.assertFalse(config.has_section("Not a section"))

    def testMissingHeader(self):
        """
        readfp() should raise MissingSectionHeaderError
        when parsing a file without section headers.
        """
        data = StringIO.StringIO("\nSetting=1\n")

        config = ConfigParser.ConfigParser()
        self.assertRaises(ConfigParser.MissingSectionHeaderError,
                          config.readfp, data)

if __name__ == "__main__":
    unittest.main()
And a simple write configuration test:
import unittest
import StringIO
import ConfigParser

class WriteConfigTest(unittest.TestCase):

    def testWriteConfig(self):
        """
        Test write a simple config
        """
        data = StringIO.StringIO()
        config = ConfigParser.ConfigParser()
        config.add_section("My Section")
        config.set("My Section", "my_option", "my_value")
        config.write(data)

        expected = "[My Section]\nmy_option = my_value\n\n"
        self.assertEquals(expected, data.getvalue())

if __name__ == "__main__":
    unittest.main() 
These unittests are of course only examples and do not fully test the ConfigParser class.
 
It's easy to see that file-like objects are very handy and fits well in frameworks and APIs. There are of course many advantages of using an abstraction when reading/writing data, some of them are:
  • Implementations do not depend on real files, socket, etc, only on a file-like object.
  • Easier to fake data in unittests.
  • Caller responsible of opening/closing the resource.
  • Less error-handling in framework code, delegate exceptions to the caller.
The downside with the current file-like object abstraction is that it's tailored for text I/O. In Python 2.6 and 3.x a new module have been added named io. The module originates from the New I/O PEP. The module enriches Python with an three-layer solution, the raw I/O layer, the buffered I/O layer and finally the text I/O layer. The raw and buffered layers deals with units of bytes and the text layer deals with units of characters. There are some things you should be aware of if you use the new I/O library in Python 2.6, read more about it here.

I've implemented a shoutcast playlist parser, plsparser, using the ConfigParser class. The parser is implemented as a Python generator function. Actually, I might do a post about generator functions someday.