Flourish PHP Unframework
This is an archived copy of the forum for reference purposes

fFixture - easy record creation for unit testing

posted by mblarsen 7 years ago

Hi - I've just pushed my fFixture class to github https://github.com/mblarsen/fFixture.

fFixture provides an easy way of creating sample data (fixtures) for unit testing using Flourish.

It recursively creates records based on JSON fixture files that you provide. By default it will create records for all fixture files it finds, but you can specify to use only selected fixtures.

Usage

Create JSON fixture files like this users.json:

	[
		{
			"user_id": 1,
			"name": "Will Bond",
			"contribution": "Creator of Flourish"
		}
	]

Create a fixture instance:

fFixture::setDatabase($db);
$fixture = fFixture::create('/path/to/fixture/');

$fixture->build();

Use your newly created objects:

$user = new User(1);
echo $user->getName() // outputs: Will Bond

Bring everything down again:

$fixture->tearDown();

That's pretty much it. Hope you like it -- feedback is more than welcome :)

Check out blog post on same topic

"Yeah but I got a lot of data already that I want to use for testing, what about that?"

You can use the fORMJSON::extend() method to extend fActiveRecord with a toJSON() method. It also extends fRecordSet. Pretty cool stuff.

I'm considering adding a fFixture::dump($root, $tables) method that uses these methods to build JSON fixture files. Maybe with some limiting options in case of huge datasets.

posted by mblarsen 7 years ago

Very useful, I think I'll be using this a lot!

Feedback to come when I fully test it. :)

posted by rirez 7 years ago

Feedback would be awesome. I wrote this as writing test data using PHP (new Record()) quickly took up a lot of lines compared to the actual testcode, making the code hard to read. Also the DRY principle was hard to uphold. We did write helper/factory classes in some cases, but even these classes started to look alike.

Having the fixture files really helps a lot. The data is easy to maintain and to reuse, but it also makes it easy to quickly identify problems with your data in case of failing tests.

In some cases we need lots and lots of records and we create these using PHP within the JSON files. But we've discovered that this breaks the "cleanliness" we had achieved with these fixtures in the first place. String building is error prone and hard to read.

So we are now working on a way to build fixtures using specifications. Similar to the fixture files but programmatically build.

		$seed = new fFixtureSeed($this->db, 'products', 100);
		
		$seed->catalog('movie')
			->version('DVD', 'Blu-Ray')
			->shopId(1)
			->name(function ($seed, $number) { return 'Cool Product #' . $number; })
			->child('offerings', 2)->price(49.95, 69.95, 79.95, 119.95, 249.95)->random()
				->version(function ($seed, $num) { return $seed->up('product')->object()->getVersion(); })
				->child('price_tiers', 3)
					->minUnits(3,5,10)
					->price(function ($seed, $num) { /* mark up by 10, 12, 15% */})
					->up('products')
			->child('product_descriptions', 2)
		    	->description('Lorem ipsum dolor sit amet');

This will build 100 movie products. 50 DVDs and 50 Blu-rays. For each product there will be two offerings with a random price. The offering version will be the same as the product version. Each offering has three price tiers that are a mark up from the randomly selected price of 10, 12, 15%. I think you get the point.

Too us this seems like a better way to fill up your fixture files with PHP.

In this example (from the unit tests) they are called seeds but will likely be renamed specs. But the point is that you specify the properties of your records that is relevant to you, and leave it to flourish to fill out the rest. These specs can work along side with your fixture files.

Could this be useful for you as well? Any thoughts?

posted by mblarsen 7 years ago

It does look interesting and seems a lot more powerful, but I see a learning curve. Since the main feature is the ability to create many different records of a type, why not just define it in JSON instead?

[
{
    "user_id": "increment",
    "name": {"select": "iterate", "data": ["Alice", "Bob", "Bill", "John"]},
    "contribution": {"select": "random", "repeat": 2, "data": ["foo", "bar", "fudge", "baby hippos"]}
}
]

Which would be easily expandable by adding more 'select' types. You could just have a type like "randomInt" and define a max/min, or stuff like that. I think this personally makes sense to me because I've always seen JSON as a data format, and PHP as a processing language, which makes it odd to see data structures built in PHP.

posted by rirez 7 years ago

I absolutely agree that the JSON files are for the data and PHP is for the processing part, and I would keep as much as possible that relates to a single type of record within these files. I have considered the option to have hierarchical data in stead of always splitting relations into multiple files. However, I'm unsure if I want to go in the direction of a format within a format, when it comes to having specifications as actual JSON. This would mean that you would have to process the actual values and furthermore I think it could clutter up the simple view of the data even more. So to achieve iterate and random like functionality, I would still rely on PHP - then it will be clear to me when it is data and when it is process.

But the real incentive to build specifications is the need to be able to specify exact properties across relations, say from A to C, without having to bother about B and all of the properties of either A, B, and C. This is hard to manage with multiple files - you loose focus on the important parts. Even with hierarchal JSON files like you suggests it could become messy.

$spec = new Spec('users', 4);
$spec->name("Alice", "Bob", "Bill", "John")->contribution("foo", "bar", "fudge", "baby hippos")->random();

This is the example you produced as I understood it. Since it has no relations it is pretty simple. Lets assume the contribution is a relation:

$spec = new Spec('users', 4);
$spec->name("Alice", "Bob", "Bill", "John")->child('contribution', 2)->title("foo", "bar", "fudge", "baby hippos")->random();

Not much more complicated. The case that we could really use specs is where you have some data requirements across relations - e.g. the 'version' in my previous post. An other example could be the case where I would like to test my factory method for getting products from a category that is for sale at the moment. My module could look like this:

   category * .. * product
   product 1 .. * offering

Category is self-explanatory. Offering records are price and date, products are description, codes, etc. So what I'm interested in here is to produce test data where I can assert that a certain number of products from category has offerings are for sale at the moment. So really we don't really bother about the product qualities.

It could look like this:

$spec = new Spec('categories', 2)
$spec->categoryId(1, 2)->child('offerings', 1)->validFrom(strtotime("+1 day"), strtotime("-1 day");

This would give me a total of 2 offerings, since the default is 1 record pr. relation. But the point is we dont have to specify anything about the 'man in the middle'. Lets do it anyway:

$spec = new Spec('categories', 2)
$spec->categoryId(1, 2)->child('products', 20)->child('offerings', 1)->validFrom(strtotime("+1 day"), strtotime("-1 day");

The simplest form of a child spec - its quantity. So now we will get a total of 40 offerings 10 of them good for my query.

Thanks for your feedback - have you tried it out yet? Have tried using PHP in the json files?

posted by mblarsen 7 years ago