Q:

Improving CakePHP 3 Unit Test Performance

Unit tests are painfully slow for my CakePHP 3.5 project. We have a large amount of data that must be loaded into fixtures in order to adequately test our application. We have 50 fixtures that are loaded up in 5 different Integration Test classes, and 78 total tests across those classes. It takes several minutes for these to run.
The fixtures schema is loaded from our database, with records populate using the records public variable, example:
class AmenityFixture extends TestFixture
{
    public $table = 'amenity';
    public $import = ['connection' => 'default', 'model' => 'Amenity'];
    /**
     * Init method
     *
     * @return void
     */
    public function init()
    {
        $this->records = [
            [
                'id' => 1,
                'amenity_category_id' => 8,
                'name' => 'Air conditioning',
                'slug' => 'air-conditioning',
                'status' => 'active',
                'created' => '2013-06-03 11:07:30',
                'modified' => '2013-06-18 12:17:29'
            ],

Is it possible, to just load the fixtures once, perhaps through  tests/boostrap.php and then tear them down elsewhere once testing exits? I have seen references to this sort of thing but only for Cake 2.
More Info
I know its the fixtures because if I force the fixtures to be created by exiting out of the setup method, then comment out the fixtures, the tests run in 1.23 seconds for our largest integration test controller. If it has to create the fixtures from scratch, then it takes over 2 minutes. I am wondering if Cake is creating the fixtures for each test or just per controller? I only need these fixtures to run once, even just running once per controller (if they are indeed running per test method) would be a huge improvement.
Here is an example of a test class and sample test method:
<?php
namespace Api\Test\TestCase\Controller;

use Api\Controller\BookController;
use Cake\TestSuite\IntegrationTestCase;
use Cake\Network\Http\Client;
use Cake\Utility\Security;
use Cake\Core\Configure;

use App\Logic\BookingLogic;
use App\Legacy\CodeIgniter\CI_Encrypt;

/**
 * Api\Controller\DefaultController Test Case
 * @example vendor/bin/phpunit plugins/Api
 */
class BookControllerTest extends IntegrationTestCase
{
    /**
     * Fixtures
     *
     * @var array
     */
    public $fixtures = [
        'app.amenity',
        'app.amenity_category',
        'app.api_log',
        'app.area',
        'app.area_filter',
        'app.area_geocode',
        'app.area_publisher',
        'app.area_property',
        'app.country',
        'app.discount',
        'app.error_log',
        'app.feature',
        'app.filter',
        'app.organization',
        'app.publisher',
        'app.publisher_key',
        'app.publisher_property',
        'app.publisher_property_feature',
        'app.publisher_property_order',
        'app.promotion',
        'app.promotion_blackout',
        'app.promotion_property_accommodation',
        'app.promotion_purchase_requirement',
        'app.promotion_type',
        'app.promotion_stay_requirement',
        'app.property',
        'app.property_accommodation',
        'app.property_accommodation_publisher_exception',
        'app.property_accommodation_rate',
        'app.property_accommodation_rate_log',
        'app.property_accommodation_rate_availability',
        'app.property_accommodation_rate_availability_log',
        'app.property_accommodation_image',
        'app.property_accommodation_provider_attribute_value',
        'app.property_accommodation_type',
        'app.property_address',
        'app.property_address_geocode',
        'app.property_amenity',
        'app.property_description',
        'app.property_discount',
        'app.property_discount_option',
        'app.property_image',
        'app.property_fee',
        'app.property_filter',
        'app.property_policy',
        'app.property_provider',
        'app.property_provider_attribute_value',
        'app.property_rating',
        'app.property_telephone',
        'app.property_type',
        'app.provider',
        'app.provider_attribute',
        'app.reservation',
        'app.reservation_confirmation',
        'app.reservation_customer',
        'app.reservation_customer_address',
        'app.reservation_customer_telephone',
        'app.reservation_idx',
        'app.reservation_payment',
        'app.reservation_promotion',
        'app.reservation_property_accommodation',
        'app.reservation_property_accommodation_discount',
        'app.reservation_property_accommodation_rate',
        'app.state',
    ];

    public function setUp(){

        $this->checkin = date('Y-m-d', strtotime('+2 days'));
        $this->checkout = date('Y-m-d', strtotime('+6 days'));
        $this->configRequest([
            'headers' => ['Accept' => 'application/json']
        ]);
    }

    public function testAvailability(){
        $this->post('/publisher/v3.0/book/preview.json',json_encode([
            'property_id' => 1111,
            'accommodation_id' => 1303,
            'rate_code' => 'REZ',
            'checkin' => $this->checkin,
            'checkout' => $this->checkout,
            'rooms' => [
                ['adults' => 2, 'children' => 0],
            ]
        ]));
        $this->assertEquals(958.64, $rate->total->total);
    }

A:

I changed how our unit/integration tests work. This works around setting up and tearing down the database for each test case (which is an INSANE no-no for big O might I add). This a long answer:
tests/bootstrap.php
We are going to define our fixtures in the bootstrap file and build them up using a new class that extends Fixture Manager (more on this later). At the end of the bootstrap file we are going to force the connection to use our test database.
<?php
use App\Logic\FixtureMaker;
use Cake\Datasource\ConnectionManager;

require dirname(__DIR__) . '/config/bootstrap.php';

$fixtures = [
    'app.all_your_table_fixtures',
    'app.right_here',
];

$connection = ConnectionManager::get('test');
$connection->disableForeignKeys();
$fixtureMaker = new FixtureMaker();

echo "\r\nBuilding fixtures\r\n";

foreach ($fixtures as $fixture) {
    $table = str_replace('app.', '', $fixture);
    $model = Cake\Utility\Inflector::camelize($table);
    $fixture = $model . 'Fixture';
    $fixtureMaker->pushFixture($model, $fixture);
    $fixtureMaker->loadSingle($model, $connection, false);
}
unset($fixtureMaker); // i was getting pdo errors without this
unset($connection);

echo "\r\nFixtures completed\r\n";

ConnectionManager::alias('test', 'default');

FixtureMaker
You can put this anywhere, for us its in the App\Logic\FixtureMaker namespace. It just needs to extend FixtureManager so we can use its protected methods, namely loadSingle(). We also need to populate the protected attribute _loaded and _fixtureMap. Pretty basic.
<?php

namespace App\Logic;

use Cake\TestSuite\Fixture\FixtureManager;

class FixtureMaker extends FixtureManager
{
    public function pushFixture($class, $fixture)
    {
        $className = "App\\Test\\Fixture\\" . $fixture;
        $this->_loaded[$fixture] = new $className();
        $this->_fixtureMap[$class] = $this->_loaded[$fixture];
    }
}

phpunit.xml.dist
Nothing crazy here, just remove the listeners that Cake puts in there by default. If these listeners are left in there then Cake tries to slap in its own fixtures and such, we don't want that.
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
    colors="true"
    processIsolation="false"
    stopOnFailure="true"
    syntaxCheck="false"
    bootstrap="./tests/bootstrap.php"
    >
    <php>
        <ini name="memory_limit" value="-1"/>
        <ini name="apc.enable_cli" value="1"/>
    </php>

    <!-- Add any additional test suites you want to run here -->
    <testsuites>
        <testsuite name="App Test Suite">
            <directory>./tests/TestCase</directory>
            <directory>./plugins/Api/tests</directory>
            <directory>./plugins/Adapter/tests</directory>
        </testsuite>
        <!-- Add plugin test suites here. -->
    </testsuites>

    <!-- Setup a listener for fixtures -->
    <listeners>
    </listeners>

    <!-- Ignore vendor tests in code coverage reports -->
    <filter>
        <whitelist>
            <directory suffix=".php">./src/</directory>
            <directory suffix=".php">./plugins/*/src/</directory>
        </whitelist>
    </filter>
</phpunit>

Fixtures
Remove all the references to your fixtures from your various test classes. Your actual fixture classes can be done however you want I suppose, for me I elected to read in the production schema but create custom records. This is preferable because I never have to update fixture schema. I should only need to modify an existing fixture if need to add/edit a record for a test. Here is an example fixture:
<?php
namespace App\Test\Fixture;

use Cake\TestSuite\Fixture\TestFixture;

/**
 * PromotionTypeFixture
 *
 */
class PromotionTypeFixture extends TestFixture
{
    public $table = 'promotion_type';
    public $import = ['connection' => 'default', 'model' => 'PromotionType'];

    /**
     * Init method
     *
     * @return void
     */
    public function init()
    {
        $this->records = [
            [
                'id' => 1,
                'name' => '$ off',
                'created' => '2012-11-05 13:02:09'
            ],
            [
                'id' => 2,
                'name' => '% off',
                'created' => '2012-11-05 13:02:14'
            ],
            [
                'id' => 3,
                'name' => 'Free Night',
                'created' => '2012-11-05 13:02:18'
            ],
        ];
        parent::init();
    }
}

Last word
This was a huge pain to figure out, but it dropped our execution time for unit testing from nearly 3 minutes (and growing at a brutal rate) to 13 seconds. We do have a decent amount of tests here: 
Time: 13.13 seconds, Memory: 36.00MB
OK, but incomplete, skipped, or risky tests!
Tests: 111, Assertions: 802, Incomplete: 21.

