Example Test SQLite Assets

From OpenSimulator

Jump to: navigation, search


An Example Test - SQLite Assets

using System;
using System.IO;
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
using OpenSim.Framework;
using OpenSim.Data.Tests;
using OpenSim.Data.SQLite;
using OpenSim.Region.Environment.Scenes;
using OpenMetaverse;
 
namespace OpenSim.Data.SQLite.Tests
{
    [TestFixture]
    public class SQLiteAssetTest
    {
        public string file;
        public string connect;
        public AssetDataBase db;
        public UUID uuid1;
        public UUID uuid2;
        public UUID uuid3;
        public string name1;
        public string name2;
        public string name3;
        public byte[] asset1;
 
        [TestFixtureSetUp]
        public void Init()
        {
            uuid1 = UUID.Random();
            uuid2 = UUID.Random();
            uuid3 = UUID.Random();
            name1 = "asset one";
            name2 = "asset two";
            name3 = "asset three";
 
            asset1 = new byte[100];
            asset1.Initialize();
            file = Path.GetTempFileName() + ".db";
            connect = "URI=file:" + file + ",version=3";
            db = new SQLiteAssetData();
            db.Initialise(connect);
        }
 
        [TestFixtureTearDown]
        public void Cleanup()
        {
            db.Dispose();
            System.IO.File.Delete(file);
        }
 
        [Test]
        public void T001_LoadEmpty()
        {
            Assert.That(db.ExistsAsset(uuid1), Is.False);
            Assert.That(db.ExistsAsset(uuid2), Is.False);
            Assert.That(db.ExistsAsset(uuid3), Is.False);
        }
 
        [Test]
        public void T010_StoreSimpleAsset()
        {
            AssetBase a1 = new AssetBase(uuid1, name1);
            AssetBase a2 = new AssetBase(uuid2, name2);
            AssetBase a3 = new AssetBase(uuid3, name3);
            a1.Data = asset1;
            a2.Data = asset1;
            a3.Data = asset1;
 
            db.CreateAsset(a1);
            db.CreateAsset(a2);
            db.CreateAsset(a3);
 
            AssetBase a1a = db.FetchAsset(uuid1);
            Assert.That(a1a.ID, Is.EqualTo(uuid1));
            Assert.That(a1a.Name, Is.EqualTo(name1));
 
            AssetBase a2a = db.FetchAsset(uuid2);
            Assert.That(a2a.ID, Is.EqualTo(uuid2));
            Assert.That(a2a.Name, Is.EqualTo(name2));
 
            AssetBase a3a = db.FetchAsset(uuid3);
            Assert.That(a3a.ID, Is.EqualTo(uuid3));
            Assert.That(a3a.Name, Is.EqualTo(name3));
        }
 
        [Test]
        public void T011_ExistsSimpleAsset()
        {
            Assert.That(db.ExistsAsset(uuid1), Is.True);
            Assert.That(db.ExistsAsset(uuid2), Is.True);
            Assert.That(db.ExistsAsset(uuid3), Is.True);
        }
    }
}

You can see 4 of the important annotations here:

  • TestFixture - this class is a test suite
  • TestFixtureSetup - this code is always run before any of the tests are executed
  • TestFixtureTearDown - this code is always run after the tests are done executing, even if they fail.
  • Test - this method is a test

This Test is Flawed

There is also a flaw in the tests above, namely the subject under test (the SQLiteAssetData object db) is used in the tests as well as the fixture setup. Consider the following scenario: the SQLiteAssetData has a caching mechanism keeps track of DB rows in memory. When rows are fetched, they are stored into the cache. When new rows are inserted or existing rows updated, the cache is updated, and eventually the cache is synced to disk.

Note that in the test above, all interactions with the database are handled by this SQLiteAssetData object. First, it's used to create the database and tables:

[TestFixture]
public class SQLiteAssetTest
{
    // ...
    public AssetDataBase db;
 
    [TestFixtureSetUp]
    public void Init()
    {
        // ...
        connect = "URI=file:" + Path.GetTempFileName() + ".db,version=3";
        db = new SQLiteAssetData();
        db.Initialise(connect);
    }
 
    [TestFixtureTearDown]
    public void Cleanup()
    {
        db.Dispose();
        // ...
    }

Then, in T010_StoreSimpleAsset(), db.CreateAsset() is called, with the intent to persist the asset to the database on disk. However to verify whether this operation succeeded, db.FetchAsset() is called:

[Test]
    public void T010_StoreSimpleAsset()
    {
        // ...
        db.CreateAsset(a1);
        db.CreateAsset(a2);
        db.CreateAsset(a3);
 
        AssetBase a1a = db.FetchAsset(uuid1);
        Assert.That(a1a.ID, Is.EqualTo(uuid1));
        Assert.That(a1a.Name, Is.EqualTo(name1));
 
        AssetBase a2a = db.FetchAsset(uuid2);
        Assert.That(a2a.ID, Is.EqualTo(uuid2));
        Assert.That(a2a.Name, Is.EqualTo(name2));
 
        AssetBase a3a = db.FetchAsset(uuid3);
        Assert.That(a3a.ID, Is.EqualTo(uuid3));
        Assert.That(a3a.Name, Is.EqualTo(name3));
    }
}

Now assume a change introduced a bug that prevent the cache from being flushed to disk ever. The tests above would never discover this bug!

How to Fix the Flaw

Another means of populating the database and verifying the success of the operations performed by SQLiteAssetData object should be used instead. For example:

[TestFixture]
public class SQLiteAssetTest
{
    // ...
    public string filename, connect;
    public AssetDataBase db;
 
    [TestFixtureSetUp]
    public void Init()
    {
        // ...
        filename = Path.GetTempFileName() + ".db";
        connect = "URI=file:" + filename + ",version=3";
 
        SQLiteDB sqldb = new SQLiteDBAdapter(connect); // ficticious adapter interfacing directly with SQLite database
        sqldb.createTable(new SQLiteTable(...));
        sqldb.executeSQL("INSET INTO assets VALUES(...)");
 
        db = new SQLiteAssetData();
    }
 
    [TestFixtureTearDown]
    public void Cleanup()
    {
        db.Dispose();
        System.IO.File.Delete(filename);
        // ...
    }

Similarly, to verify that the SQLiteAssetData object creates and retrieves assets properly, test T010_StoreSimpleAsset() might be broken up into two tests:

[Test]
    public void T010_StoreSimpleAsset()
    {
        AssetBase a1, a2, a3;
        // initialize a1, a2 and a3
 
        db.CreateAsset(a1);
        db.CreateAsset(a2);
        db.CreateAsset(a3);
 
        SQLiteDB sqldb = new SQLiteDBAdapter(connect); // ficticious adapter interfacing directly with SQLite database
        AssetBase a1_actual = sqldb.executeSQL("SELECT * FROM assets WHERE uuid={0}", a1.uuid);
 
        Assert.Equals(a1_actual.uuid, a1.uuid);
        Assert.Equals(a1_actual.Name, a1.Name);
        // etc
    }
 
    [Test]
    public void T011_FetchSimpleAsset()
    {
        AssetBase a1 = new AssetBase(uuid1, name1, ...);
 
        SQLiteDB sqldb = new SQLiteDBAdapter(connect); // ficticious adapter interfacing directly with SQLite database
        AssetBase a1_actual = sqldb.executeSQL("INSERT INTO assets VALUES({0}, ...)", a1.uuid, ...);
        // etc
 
        // ...
        AssetBase a1a = db.FetchAsset(uuid1);
        Assert.That(a1a.ID, Is.EqualTo(uuid1));
        Assert.That(a1a.Name, Is.EqualTo(name1));
 
        AssetBase a2a = db.FetchAsset(uuid2);
        Assert.That(a2a.ID, Is.EqualTo(uuid2));
        Assert.That(a2a.Name, Is.EqualTo(name2));
 
        AssetBase a3a = db.FetchAsset(uuid3);
        Assert.That(a3a.ID, Is.EqualTo(uuid3));
        Assert.That(a3a.Name, Is.EqualTo(name3));
    }
}

This way T010_StoreSimpleAsset tests SQLiteAssetData.CreateAsset() only and T011_FetchSimpleAsset() tests SQLiteAssetData.FetchAsset() only. If there are any problems in either of those methods or all of them, these tests will discover that there is a problem.

Personal tools
General
About This Wiki