diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php
index 08452f3..71c5b5c 100644
--- a/modules/simpletest/drupal_web_test_case.php
+++ b/modules/simpletest/drupal_web_test_case.php
@@ -45,6 +45,20 @@ abstract class DrupalTestCase {
   protected $timeLimit = 500;
 
   /**
+   * Whether to cache the installation part of the setUp() method.
+   *
+   * @var bool
+   */
+  public $useSetupInstallationCache = FALSE;
+
+  /**
+   * Whether to cache the modules installation part of the setUp() method.
+   *
+   * @var bool
+   */
+  public $useSetupModulesCache = FALSE;
+
+  /**
    * Current results of this test case.
    *
    * @var Array
@@ -1424,6 +1438,181 @@ class DrupalWebTestCase extends DrupalTestCase {
   }
 
   /**
+   * Copies the cached tables and files for a cached installation setup.
+   *
+   * @param string $cache_key_prefix
+   *   (optional) Additional prefix for the cache key.
+   *
+   * @return bool
+   *   TRUE when the cache was usable and loaded, FALSE when cache was not
+   *   available.
+   *
+   * @see DrupalWebTestCase::setUp()
+   */
+  protected function loadSetupCache($cache_key_prefix = '') {
+    $cache_key = $this->getSetupCacheKey($cache_key_prefix);
+    $cache_file = $this->originalFileDirectory . '/simpletest/' . $cache_key . '/simpletest-cache-setup';
+
+    if (file_exists($cache_file)) {
+      return $this->copySetupCache($cache_key, substr($this->databasePrefix, 10));
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * Returns the cache key used for the setup caching.
+   *
+   * @param string $cache_key_prefix
+   *   (optional) Additional prefix for the cache key.
+   *
+   * @return string
+   *   The cache key to use, by default only based on the profile used by the
+   *   test.
+   */
+  protected function getSetupCacheKey($cache_key_prefix = '') {
+    // The cache key needs to start with a numeric character, so that the cached
+    // installation gets cleaned up properly.
+    $cache_key_prefix = hash('crc32b', $cache_key_prefix . $this->profile) . '_';
+    return '1c_' . $cache_key_prefix . $this->profile;
+  }
+
+  /**
+   * Store the installation setup to a cache.
+   *
+   * @param string $cache_key_prefix
+   *   (optional) Additional prefix for the cache key.
+   *
+   * @return bool
+   *   TRUE if the installation was stored in the cache, FALSE otherwise.
+   */
+  protected function storeSetupCache($cache_key_prefix = '') {
+    $cache_key = $this->getSetupCacheKey($cache_key_prefix);
+
+    // Use a special semaphore table that exists only in the cached tables.
+    $cache_semaphore_table = $cache_key . 'simpletest_semaphore';
+
+    // This function can be called concurrently when several tests try to store
+    // the same cache. Also a cache can be left in an unclean state in case
+    // that the user interrupted the copy process. Therefore failing to create
+    // the special cache semaphore table is expected as it might already exist.
+    try {
+      $semaphore_source_table = $this->databasePrefix . 'semaphore';
+      db_query('CREATE TABLE ' . $cache_semaphore_table . '' . ' LIKE ' . $semaphore_source_table);
+    }
+    catch (Exception $e) {
+    }
+
+    // All concurrent tests share the same test id. Therefore it is possible to
+    // use the special cache semaphore table to ensure that only one process
+    // will store the cache. This is important as else DB tables created by one
+    // process could be deleted by another as the cache copying is idempotent.
+    try {
+      self::getDatabaseConnection()
+        ->insert($cache_semaphore_table)
+        ->fields(array(
+          'name' => 'simpletest_store_cache_' . $this->testId,
+          'value' => $cache_key,
+          'expire' => microtime(TRUE),
+        ))
+        ->execute();
+    }
+    catch (Exception $e) {
+      return FALSE;
+    }
+
+    // Try to copy the installation to the setup cache - now that we have a
+    // lock to do so.
+    if (!$this->copySetupCache(substr($this->databasePrefix, 10), $cache_key)) {
+      // It is non-fatal if the cache cannot be copied as the next test run
+      // will try it again.
+      $this->assert('debug', t('Storing cache with key @key failed', array('@key' => $cache_key)), 'storeSetupCache');
+
+      return FALSE;
+    }
+
+    // Inform others that this cache is usable now.
+    $cache_file = $this->originalFileDirectory . '/simpletest/' . $cache_key . '/simpletest-cache-setup';
+    file_put_contents($cache_file, time(NULL));
+
+    return TRUE;
+  }
+
+  /**
+   * Copy the setup cache from/to another table and files directory.
+   *
+   * @param string $from
+   *   The prefix_id / cache_key from where to copy.
+   * @param string $to
+   *   The prefix_id / cache_key to where to copy.
+   *
+   * @return bool
+   *   TRUE if the setup cache was copied to the current installation, FALSE
+   *   otherwise.
+   */
+  protected function copySetupCache($from, $to) {
+    $from_prefix = 'simpletest' . $from;
+    $to_prefix = 'simpletest' . $to;
+
+    try {
+       $tables = db_query("SHOW TABLES LIKE :prefix", array(':prefix' => db_like($from_prefix) . '%' ))->fetchCol();
+
+       foreach ($tables as $from_table) {
+         $table = substr($from_table, strlen($from_prefix));
+         $to_table = $to_prefix . $table;
+
+         // Do not copy our internal semaphore table.
+         if ($table == 'simpletest_semaphore') {
+           continue;
+         }
+
+         // Remove the table in case the copying process was interrupted.
+         db_query('DROP TABLE IF EXISTS ' . $to_table);
+         db_query('CREATE TABLE ' . $to_table . ' LIKE ' . $from_table);
+         db_query('ALTER TABLE ' . $to_table . ' DISABLE KEYS');
+         db_query('INSERT ' . $to_table . ' SELECT * FROM ' . $from_table);
+         db_query('ALTER TABLE ' . $to_table . ' ENABLE KEYS');
+       }
+    }
+    catch (Exception $e) {
+      return FALSE;
+    }
+
+    $from_dir = $this->originalFileDirectory . '/simpletest/' . $from;
+    $to_dir = $this->originalFileDirectory . '/simpletest/' . $to;
+    $this->recursiveDirectoryCopy($from_dir, $to_dir);
+
+    return TRUE;
+  }
+
+  /**
+   * Recursively copy one directory to another.
+   *
+   * @param $src
+   *   The source directory.
+   * @param $dest
+   *   The destination directory.
+   */
+  protected function recursiveDirectoryCopy($src, $dst) {
+    $dir = opendir($src);
+
+    if (!file_exists($dst)){
+      mkdir($dst);
+    }
+    while (($file = readdir($dir)) !== FALSE) {
+      if ($file != '.' && $file != '..') {
+        if (is_dir($src . '/' . $file)) {
+          $this->recursiveDirectoryCopy($src . '/' . $file, $dst . '/' . $file);
+        }
+        else {
+          copy($src . '/' . $file, $dst . '/' . $file);
+        }
+      }
+    }
+    closedir($dir);
+  }
+
+  /**
    * Sets up a Drupal site for running functional and integration tests.
    *
    * Generates a random database prefix and installs Drupal with the specified
@@ -1476,57 +1665,108 @@ class DrupalWebTestCase extends DrupalTestCase {
     // profile's hook_install() and other hook implementations are never invoked.
     $conf['install_profile'] = $this->profile;
 
-    // Perform the actual Drupal installation.
-    include_once DRUPAL_ROOT . '/includes/install.inc';
-    drupal_install_system();
+    $has_installation_cache = FALSE;
+    $has_modules_cache = FALSE;
 
-    $this->preloadRegistry();
+    if ($this->useSetupModulesCache) {
+      $modules = func_get_args();
+      // Modules can be either one parameter or multiple.
+      if (isset($modules[0]) && is_array($modules[0])) {
+        $modules = $modules[0];
+      }
+      $modules = array_unique($modules);
+      sort($modules);
 
-    // Set path variables.
-    variable_set('file_public_path', $this->public_files_directory);
-    variable_set('file_private_path', $this->private_files_directory);
-    variable_set('file_temporary_path', $this->temp_files_directory);
+      $modules_cache_key_prefix = hash('crc32b', serialize($modules)) . '_';
+      $has_modules_cache = $this->loadSetupCache($modules_cache_key_prefix);
+    }
 
-    // Set the 'simpletest_parent_profile' variable to add the parent profile's
-    // search path to the child site's search paths.
-    // @see drupal_system_listing()
-    // @todo This may need to be primed like 'install_profile' above.
-    variable_set('simpletest_parent_profile', $this->originalProfile);
+    if (!$has_modules_cache && $this->useSetupInstallationCache) {
+      $has_installation_cache = $this->loadSetupCache();
+    }
 
-    // Include the testing profile.
-    variable_set('install_profile', $this->profile);
-    $profile_details = install_profile_info($this->profile, 'en');
+    if ($has_modules_cache || $has_installation_cache) {
+      // Reset path variables.
+      variable_set('file_public_path', $this->public_files_directory);
+      variable_set('file_private_path', $this->private_files_directory);
+      variable_set('file_temporary_path', $this->temp_files_directory);
+      $this->refreshVariables();
 
-    // Install the modules specified by the testing profile.
-    module_enable($profile_details['dependencies'], FALSE);
+      // Load all enabled modules
+      module_load_all();
 
-    // Install modules needed for this test. This could have been passed in as
-    // either a single array argument or a variable number of string arguments.
-    // @todo Remove this compatibility layer in Drupal 8, and only accept
-    // $modules as a single array argument.
-    $modules = func_get_args();
-    if (isset($modules[0]) && is_array($modules[0])) {
-      $modules = $modules[0];
-    }
-    if ($modules) {
-      $success = module_enable($modules, TRUE);
-      $this->assertTrue($success, t('Enabled modules: %modules', array('%modules' => implode(', ', $modules))));
+      $this->pass(t('Using cache: @cache (@key)', array(
+        '@cache' => $has_modules_cache ? t('Modules Cache') : t('Installation Cache'),
+        '@key' => $this->getSetupCacheKey($has_modules_cache ? $modules_cache_key_prefix : ''),
+      )));
     }
+    else {
+      // Perform the actual Drupal installation.
+      include_once DRUPAL_ROOT . '/includes/install.inc';
+      drupal_install_system();
+
+      $this->preloadRegistry();
+
+      // Set path variables.
+      variable_set('file_public_path', $this->public_files_directory);
+      variable_set('file_private_path', $this->private_files_directory);
+      variable_set('file_temporary_path', $this->temp_files_directory);
+
+      // Set the 'simpletest_parent_profile' variable to add the parent profile's
+      // search path to the child site's search paths.
+      // @see drupal_system_listing()
+      // @todo This may need to be primed like 'install_profile' above.
+      variable_set('simpletest_parent_profile', $this->originalProfile);
+
+      // Include the testing profile.
+      variable_set('install_profile', $this->profile);
+      $profile_details = install_profile_info($this->profile, 'en');
 
-    // Run the profile tasks.
-    $install_profile_module_exists = db_query("SELECT 1 FROM {system} WHERE type = 'module' AND name = :name", array(
-      ':name' => $this->profile,
-    ))->fetchField();
-    if ($install_profile_module_exists) {
-      module_enable(array($this->profile), FALSE);
+      // Install the modules specified by the testing profile.
+      module_enable($profile_details['dependencies'], FALSE);
+
+      if ($this->useSetupInstallationCache) {
+        $this->storeSetupCache();
+      }
     }
 
-    // Reset/rebuild all data structures after enabling the modules.
-    $this->resetAll();
+    if (!$has_modules_cache) {
+      // Install modules needed for this test. This could have been passed in as
+      // either a single array argument or a variable number of string arguments.
+      // @todo Remove this compatibility layer in Drupal 8, and only accept
+      // $modules as a single array argument.
+      $modules = func_get_args();
+      if (isset($modules[0]) && is_array($modules[0])) {
+        $modules = $modules[0];
+      }
+      if ($modules) {
+        $success = module_enable($modules, TRUE);
+        $this->assertTrue($success, t('Enabled modules: %modules', array('%modules' => implode(', ', $modules))));
+      }
+
+      // Run the profile tasks.
+      $install_profile_module_exists = db_query("SELECT 1 FROM {system} WHERE type = 'module' AND name = :name", array(
+        ':name' => $this->profile,
+      ))->fetchField();
+      if ($install_profile_module_exists) {
+        module_enable(array($this->profile), FALSE);
+      }
+
+      // Reset/rebuild all data structures after enabling the modules.
+      $this->resetAll();
+
+      // Run cron once in that environment, as install.php does at the end of
+      // the installation process.
+      drupal_cron_run();
 
-    // Run cron once in that environment, as install.php does at the end of
-    // the installation process.
-    drupal_cron_run();
+      if ($this->useSetupModulesCache) {
+        $this->storeSetupCache($modules_cache_key_prefix);
+      }
+    }
+    else {
+      // Reset/rebuild all data structures after enabling the modules.
+      $this->resetAll();
+    }
 
     // Ensure that the session is not written to the new environment and replace
     // the global $user session with uid 1 from the new test site.
diff --git a/modules/simpletest/simpletest.module b/modules/simpletest/simpletest.module
index cf83047..3d14fee 100644
--- a/modules/simpletest/simpletest.module
+++ b/modules/simpletest/simpletest.module
@@ -592,7 +592,8 @@ function simpletest_clean_temporary_directories() {
     $files = scandir('public://simpletest');
     foreach ($files as $file) {
       $path = 'public://simpletest/' . $file;
-      if (is_dir($path) && is_numeric($file)) {
+      // Ensure that cache directories are cleaned as well.
+      if (is_dir($path) && (is_numeric($file) || strpos($file, '1c_') === 0)) {
         file_unmanaged_delete_recursive($path);
         $count++;
       }
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
index a42215e..6e8668a 100755
--- a/scripts/run-tests.sh
+++ b/scripts/run-tests.sh
@@ -156,6 +156,21 @@ All arguments are long options.
 
   --verbose   Output detailed assertion messages in addition to summary.
 
+  --cache     (Experimental) Cache result of setUp per installation profile.
+              This will create one cache entry per profile and is generally safe
+              to use.
+              To clear all cache entries use --clean.
+
+  --cache-modules
+
+              (Experimnetal) Cache result of setUp per installation profile and
+              installed modules. This will create one copy of the database
+              tables per module-combination and therefore this option should not
+              be used when running all tests. This is most useful for local
+              development of individulat test cases.
+              This option implies --cache and to clear all cache entries use
+              --clean.
+
   <test1>[,<test2>[,<test3> ...]]
 
               One or more tests to be run. By default, these are interpreted
@@ -199,6 +214,8 @@ function simpletest_script_parse_args() {
     'directory' => '',
     'color' => FALSE,
     'verbose' => FALSE,
+    'cache' => FALSE,
+    'cache-modules' => FALSE,
     'test_names' => array(),
     // Used internally.
     'test-id' => 0,
@@ -376,6 +393,8 @@ function simpletest_script_execute_batch($test_id, $test_classes) {
  * Bootstrap Drupal and run a single test.
  */
 function simpletest_script_run_one_test($test_id, $test_class) {
+  global $args;
+
   try {
     // Bootstrap Drupal.
     drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
@@ -383,6 +402,8 @@ function simpletest_script_run_one_test($test_id, $test_class) {
     simpletest_classloader_register();
 
     $test = new $test_class($test_id);
+    $test->useSetupInstallationCache = !empty($args['cache']);
+    $test->useSetupModulesCache = !empty($args['cache-modules']);
     $test->run();
     $info = $test->getInfo();
 
@@ -418,6 +439,13 @@ function simpletest_script_command($test_id, $test_class) {
   if ($args['color']) {
     $command .= ' --color';
   }
+  if ($args['cache-modules']) {
+    $command .= ' --cache --cache-modules';
+  }
+  elseif ($args['cache']) {
+    $command .= ' --cache';
+  }
+
   $command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test " . escapeshellarg($test_class);
   return $command;
 }
