Unit testing serialization

Discussion of testing theory and practice, including methodologies (such as TDD, BDD, DDD, Agile, XP) and software - anything to do with testing goes here. (Formerly "The Testing Side of Development")

Moderator: General Moderators

Post Reply
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Unit testing serialization

Post by Ambush Commander »

Hello folks, I've got a sticky unit testing situation that I'd like to resolve. I have created a class that accepts objects to be cached, which it does by serializing the object and writing it to a file.

I don't mind the file writing operation: it's quite safe and there's virtually no chance of collision. However, I'm having difficulty determining whether or not to mock the object that is going to be cached (the one that will be serialized). If I mock it, I'll end up serializing a whole kaboodle of private testing machinery that may or may not be shifting around. If I don't mock it, I have no way of stopping it from calling other dependencies... i.e. it's not a unit test anymore (for an integration test, this is fine, but I'm trying to TDD).

Perhaps the serialization has to be abstracted out and mocked, but that seems rather extreme just for a unit test. It's a single function, no bells and whistles.
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

I don't think you should mock the object being serialised and cached. In a sense the object is itself test data so it should be left alone. I usually just use a stdClass instance. If you need anything more it's more likely you'd end up testing serialize() rather than the class calling serialize(). Does the class commit itself to a specific object type?
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

create an intercept so you don't have to actually call serialize to test it. Something like comparing the cache stack (array)

Code: Select all

class TestCache extends UnitTestCase
{
    public function __construct()
    {
        parent::__construct();
    }

    public function testCacheStack ()
    {
        $cache = new CacheObject();
        $objectToBeCached = new stdClass;
        $cache->prepareForCache($objectToBeCached);
        $this->assertEqual($cache->getStack(), array($objectToBeCached));
    }
    // or compare strings
    public function testCacheString ()
    {
        $cache = new CacheObject();
        $objectToBeCached = new stdClass;
        $cache->prepareForCache($objectToBeCached);
        $this->assertEqual($cache->getSerialized(), serialize(array($objectToBeCached)));
    }        
}

Similar sort of thing when testing a DB layer - you can test what the final statement is without needing to call the respective DB function.
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

I would definitely call serialize(). It's an internal PHP function not a datasource which is supposed to consistently operate on objects. One day PHP might alter that function and I'd prefer if my Unit Tests picked it up should that happen. It's a significant class specific behaviour - avoiding a direct test would appear to hamstring the test by focusing solely on the caching aspect.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

Both tests are valid, imo. You are testing that the stack operation is correct as well as the serialization.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Quick note: PHP 4.

stdClass almost works. I'm not enforcing a specific type, so I can get away with putting something random in there. However, before being serialized, I need the object do some __sleep() style functionality. Since I'm on PHP 4, the serializer/cache class has to do this work on the object. But what this means is that methods are going to be called, and an stdClass will fatally error out.

I imagine there are two ways out of this dilemma:

1. Create a custom mock that implements the necessary interface, and destroys the reference to the test case once its done.

2. Get rid of the __sleep() style functionality (as of right now, I'd only need this sort of thing to ensure that the definition object was properly initialized, which I could make sure always happens with a little discipline)

I'm probably going to feel this out myself, but creating an intercept seems (to me, at least) a little over the top, since no processing is happening to the object: it's just being serialized straight. What would you do?
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

just mock the __sleep() method.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

I'll take that as an endorsement of the first approach.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

no, I mean literally mock the __sleep() method..

Code: Select all

class ObjectToBeMocked //dummy definition for mock creation
{
    function __sleep() {}
}

Mock::generate('ObjectToBeMocked', 'MockObject');

class TestBlah extends UnitTestCase
{
    function TestBlah ()
    {
        $this->UnitTestCase();
    }

    function testMockCache ()
    {
        $cache =& new Cache();
        $mock =& new MockObject($this);
        $mock->expectOnce('__sleep');
        $cache->cache($mock);
    }
}
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

But then I'll end up serializing all that mock class machinery and stuff (and who knows, maybe some recursive references, which wouldn't be good).
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

Why wouldn't it be good? :?

You're testing.. not running live.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

It's mostly because I've var_dump'ed a mock object before accidentally. They're not pretty.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

Quite, but you don't have to manually type it all out; all you are looking to do is test that your object is performing as expected - i.e. it is serializing a given parameter and writing it to a file. You're not the one who is actually serailizing, that's down to PHP's serialize function, so you can do:

Code: Select all

$this->assertEqual(file_get_contents($theCacheFile), serialize($theObjectBeingSerialized));
Post Reply