diff --git a/core/lib/Drupal/Core/Utility/Error.php b/core/lib/Drupal/Core/Utility/Error.php
index 6cdc7e3..f9cc168 100644
--- a/core/lib/Drupal/Core/Utility/Error.php
+++ b/core/lib/Drupal/Core/Utility/Error.php
@@ -89,9 +89,12 @@ public static function decodeException(\Exception $exception) {
    */
   public static function renderExceptionSafe(\Exception $exception) {
     $decode = static::decodeException($exception);
+    $backtrace = $decode['backtrace'];
     unset($decode['backtrace']);
 
-    return String::format('%type: !message in %function (line %line of %file).', $decode);
+    $output = String::format('%type: !message in %function (line %line of %file).', $decode);
+    $output .= '<pre>' . static::formatBacktrace($backtrace) . '</pre>';
+    return $output;
   }
 
   /**
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
index b7848de..2575489 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
@@ -72,19 +72,21 @@ function __construct($test_id = NULL) {
   }
 
   /**
-   * Sets up Drupal unit test environment.
-   *
-   * @see \DrupalUnitTestBase::$modules
-   * @see \DrupalUnitTestBase
+   * Overrides TestBase::beforePrepareEnvironment().
    */
-  protected function setUp() {
+  protected function beforePrepareEnvironment() {
     // Copy/prime extension file lists once to avoid filesystem scans.
     if (!isset($this->moduleFiles)) {
       $this->moduleFiles = \Drupal::state()->get('system.module.files') ?: array();
       $this->themeFiles = \Drupal::state()->get('system.theme.files') ?: array();
       $this->themeData = \Drupal::state()->get('system.theme.data') ?: array();
     }
+  }
 
+  /**
+   * Sets up Drupal unit test environment.
+   */
+  protected function setUp() {
     $this->keyValueFactory = new KeyValueMemoryFactory();
 
     parent::setUp();
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index 669c615..0e32822 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -85,22 +85,6 @@
   protected $skipClasses = array(__CLASS__ => TRUE);
 
   /**
-   * Flag to indicate whether the test has been set up.
-   *
-   * The setUp() method isolates the test from the parent Drupal site by
-   * creating a random prefix for the database and setting up a clean file
-   * storage directory. The tearDown() method then cleans up this test
-   * environment. We must ensure that setUp() has been run. Otherwise,
-   * tearDown() will act on the parent Drupal site rather than the test
-   * environment, destroying live data.
-   */
-  protected $setup = FALSE;
-
-  protected $setupDatabasePrefix = FALSE;
-
-  protected $setupEnvironment = FALSE;
-
-  /**
    * TRUE if verbose debugging is enabled.
    *
    * @var boolean
@@ -790,20 +774,50 @@ public function run(array $methods = array()) {
             'function' => $class . '->' . $method . '()',
           );
           $completion_check_id = TestBase::insertAssert($this->testId, $class, FALSE, t('The test did not complete due to a fatal error.'), 'Completion check', $caller);
-          $this->setUp();
-          if ($this->setup) {
-            try {
-              $this->$method();
-              // Finish up.
-            }
-            catch (\Exception $e) {
-              $this->exceptionHandler($e);
-            }
+          try {
+            $this->prepareEnvironment();
+          }
+          catch (\Exception $e) {
+            $this->exceptionHandler($e);
+            // The prepareEnvironment() method isolates the test from the parent
+            // Drupal site by creating a random database prefix and test site
+            // directory. If this fails, a test would possibly operate in the
+            // parent site. Therefore, the entire test run for this test class
+            // has to be aborted.
+            // restoreEnvironment() cannot be called, because we do not know
+            // where exactly the environment setup failed.
+            break;
+          }
+          try {
+            $this->setUp();
+          }
+          catch (\Exception $e) {
+            $this->exceptionHandler($e);
+            // Abort if setUp() fails, since all test methods will fail.
+            // But ensure to clean up and restore the environment, since
+            // prepareEnvironment() succeeded.
+            $this->restoreEnvironment();
+            break;
+          }
+          try {
+            $this->$method();
+          }
+          catch (\Exception $e) {
+            $this->exceptionHandler($e);
+          }
+          try {
             $this->tearDown();
           }
-          else {
-            $this->fail(t("The test cannot be executed because it has not been set up properly."));
+          catch (\Exception $e) {
+            $this->exceptionHandler($e);
+            // If a test fails to tear down, abort the entire test class, since
+            // it is likely that all tests will fail in the same way and a
+            // failure here only results in additional test artifacts that have
+            // to be manually deleted.
+            $this->restoreEnvironment();
+            break;
           }
+          $this->restoreEnvironment();
           // Remove the completion check record.
           TestBase::deleteAssert($completion_check_id);
         }
@@ -835,34 +849,30 @@ public function run(array $methods = array()) {
    *
    * @see WebTestBase::curlInitialize()
    * @see drupal_valid_test_ua()
-   * @see WebTestBase::setUp()
    */
-  protected function prepareDatabasePrefix() {
+  private function prepareDatabasePrefix() {
     $this->databasePrefix = 'simpletest' . mt_rand(1000, 1000000);
 
     // As soon as the database prefix is set, the test might start to execute.
     // All assertions as well as the SimpleTest batch operations are associated
     // with the testId, so the database prefix has to be associated with it.
-    db_update('simpletest_test_id')
+    $affected_rows = db_update('simpletest_test_id')
       ->fields(array('last_prefix' => $this->databasePrefix))
       ->condition('test_id', $this->testId)
       ->execute();
+    if (!$affected_rows) {
+      throw new \RuntimeException('Failed to set up database prefix.');
+    }
   }
 
   /**
    * Changes the database connection to the prefixed one.
    *
-   * @see WebTestBase::setUp()
+   * @see TestBase::prepareEnvironment()
    */
-  protected function changeDatabasePrefix() {
+  private function changeDatabasePrefix() {
     if (empty($this->databasePrefix)) {
       $this->prepareDatabasePrefix();
-      // If $this->prepareDatabasePrefix() failed to work, return without
-      // setting $this->setupDatabasePrefix to TRUE, so setUp() methods will
-      // know to bail out.
-      if (empty($this->databasePrefix)) {
-        return;
-      }
     }
 
     // Clone the current connection and replace the current prefix.
@@ -882,9 +892,16 @@ protected function changeDatabasePrefix() {
     // @todo Fix installer to use Database connection info.
     global $databases;
     $databases['default']['default'] = $connection_info['default'];
+  }
 
-    // Indicate the database prefix was set up correctly.
-    $this->setupDatabasePrefix = TRUE;
+  /**
+   * Act on global state information before the environment is altered for a test.
+   *
+   * Allows e.g. DrupalUnitTestBase to prime system/extension info from the
+   * parent site (and inject it into the test environment so as to improve
+   * performance).
+   */
+  protected function beforePrepareEnvironment() {
   }
 
   /**
@@ -892,15 +909,26 @@ protected function changeDatabasePrefix() {
    *
    * Backups various current environment variables and resets them, so they do
    * not interfere with the Drupal site installation in which tests are executed
-   * and can be restored in TestBase::tearDown().
+   * and can be restored in TestBase::restoreEnvironment().
    *
    * Also sets up new resources for the testing environment, such as the public
    * filesystem and configuration directories.
    *
-   * @see TestBase::tearDown()
+   * This method is private as it must only be called once by TestBase::run()
+   * (multiple invocations for the same test would have unpredictable
+   * consequences) and it must not be callable or overridable by test classes.
+   *
+   * @see TestBase::beforePrepareEnvironment()
    */
-  protected function prepareEnvironment() {
+  private function prepareEnvironment() {
     global $user, $conf;
+
+    // Allow (base) test classes to backup global state information.
+    $this->beforePrepareEnvironment();
+
+    // Create the database prefix for this test.
+    $this->prepareDatabasePrefix();
+
     $language_interface = language(Language::TYPE_INTERFACE);
 
     // When running the test runner within a test, back up the original database
@@ -965,6 +993,9 @@ protected function prepareEnvironment() {
 
     // Reset statics before the old container is replaced so that objects with a
     // __destruct() method still have access to it.
+    // All static variables need to be reset before the database prefix is
+    // changed, since \Drupal\Core\Utility\CacheArray implementations attempt to
+    // write back to persistent caches when they are destructed.
     // @todo: Remove once they have been converted to services.
     drupal_static_reset();
 
@@ -998,8 +1029,13 @@ protected function prepareEnvironment() {
     $test_info['test_run_id'] = $this->databasePrefix;
     $test_info['in_child_site'] = FALSE;
 
-    // Indicate the environment was set up correctly.
-    $this->setupEnvironment = TRUE;
+    // Change the database prefix.
+    $this->changeDatabasePrefix();
+
+    // Reset all variables to perform tests in a clean environment.
+    $conf = array();
+
+    drupal_set_time_limit($this->timeLimit);
   }
 
   /**
@@ -1037,7 +1073,7 @@ protected function prepareConfigDirectories() {
    * old module list.
    *
    * @see TestBase::prepareEnvironment()
-   * @see TestBase::tearDown()
+   * @see TestBase::restoreEnvironment()
    *
    * @todo Fix http://drupal.org/node/1708692 so that module enable/disable
    *   changes are immediately reflected in \Drupal::getContainer(). Until then,
@@ -1060,6 +1096,12 @@ protected function rebuildContainer() {
 
   /**
    * Performs cleanup tasks after each individual test method has been run.
+   */
+  protected function tearDown() {
+  }
+
+  /**
+   * Cleans up the test environment and restores the original environment.
    *
    * Deletes created files, database tables, and reverts environment changes.
    *
@@ -1069,7 +1111,7 @@ protected function rebuildContainer() {
    * @see TestBase::changeDatabasePrefix()
    * @see TestBase::prepareEnvironment()
    */
-  protected function tearDown() {
+  private function restoreEnvironment() {
     global $user, $conf;
 
     // Reset all static variables.
@@ -1088,22 +1130,20 @@ protected function tearDown() {
       }
     }
 
-    // Ensure that TestBase::changeDatabasePrefix() has run and TestBase::$setup
-    // was not tricked into TRUE, since the following code would delete the
-    // entire parent site otherwise.
-    if ($this->setupDatabasePrefix) {
-      // Remove all prefixed tables.
-      $connection_info = Database::getConnectionInfo('default');
-      $tables = db_find_tables($connection_info['default']['prefix']['default'] . '%');
-      $prefix_length = strlen($connection_info['default']['prefix']['default']);
+    // Remove all prefixed tables.
+    // @todo Connection prefix info is not normalized into an array.
+    $original_connection_info = Database::getConnectionInfo('simpletest_original_default');
+    $original_prefix = is_array($original_connection_info['default']['prefix']) ? $original_connection_info['default']['prefix']['default'] : $original_connection_info['default']['prefix'];
+    $test_connection_info = Database::getConnectionInfo('default');
+    $test_prefix = is_array($test_connection_info['default']['prefix']) ? $test_connection_info['default']['prefix']['default'] : $test_connection_info['default']['prefix'];
+    if ($original_prefix != $test_prefix) {
+      $tables = Database::getConnection()->schema()->findTables($test_prefix . '%');
+      $prefix_length = strlen($test_prefix);
       foreach ($tables as $table) {
-        if (db_drop_table(substr($table, $prefix_length))) {
+        if (Database::getConnection()->schema()->dropTable(substr($table, $prefix_length))) {
           unset($tables[$table]);
         }
       }
-      if (!empty($tables)) {
-        $this->fail('Failed to drop all prefixed tables.');
-      }
     }
 
     // In case a fatal error occurred that was not in the test process read the
@@ -1380,8 +1420,9 @@ public static function generatePermutations($parameters) {
   /**
    * Ensures test files are deletable within file_unmanaged_delete_recursive().
    *
-   * Some tests chmod generated files to be read only. During tearDown() and
-   * other cleanup operations, these files need to get deleted too.
+   * Some tests chmod generated files to be read only. During
+   * TestBase::restoreEnvironment() and other cleanup operations, these files
+   * need to get deleted too.
    */
   public static function filePreDeleteCallback($path) {
     chmod($path, 0700);
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/BrokenSetUpTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/BrokenSetUpTest.php
index 19efd76..42b786a 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/BrokenSetUpTest.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/BrokenSetUpTest.php
@@ -46,41 +46,74 @@ function setUp() {
     }
     // If the test is being run from within simpletest, set up the broken test.
     else {
-      $this->pass(t('The test setUp() method has been run.'));
-      // Don't call parent::setUp(). This should trigger an error message.
+      if (file_get_contents($this->originalFileDirectory . '/simpletest/trigger') === 'setup') {
+        throw new \Exception('Broken setup');
+      }
+      $this->pass('The setUp() method has run.');
     }
   }
 
   function tearDown() {
     // If the test is being run from the main site, tear down normally.
     if (!drupal_valid_test_ua()) {
+      unlink($this->originalFileDirectory . '/simpletest/trigger');
       parent::tearDown();
     }
+    // If the test is being run from within simpletest, output a message.
     else {
-      // If the test is being run from within simpletest, output a message.
-      $this->pass(t('The tearDown() method has run.'));
+      if (file_get_contents($this->originalFileDirectory . '/simpletest/trigger') === 'teardown') {
+        throw new \Exception('Broken teardown');
+      }
+      $this->pass('The tearDown() method has run.');
     }
   }
 
   /**
    * Runs this test case from within the simpletest child site.
    */
-  function testBreakSetUp() {
+  function testMethod() {
     // If the test is being run from the main site, run it again from the web
     // interface within the simpletest child site.
     if (!drupal_valid_test_ua()) {
+      // Verify that a broken setUp() method is caught.
+      file_put_contents($this->originalFileDirectory . '/simpletest/trigger', 'setup');
+      $edit['Drupal\simpletest\Tests\BrokenSetUpTest'] = TRUE;
+      $this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests'));
+      $this->assertRaw('Broken setup');
+      $this->assertNoRaw('The setUp() method has run.');
+      $this->assertNoRaw('Broken test');
+      $this->assertNoRaw('The test method has run.');
+      $this->assertNoRaw('Broken teardown');
+      $this->assertNoRaw('The tearDown() method has run.');
+
+      // Verify that a broken tearDown() method is caught.
+      file_put_contents($this->originalFileDirectory . '/simpletest/trigger', 'teardown');
       $edit['Drupal\simpletest\Tests\BrokenSetUpTest'] = TRUE;
       $this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests'));
+      $this->assertNoRaw('Broken setup');
+      $this->assertRaw('The setUp() method has run.');
+      $this->assertNoRaw('Broken test');
+      $this->assertRaw('The test method has run.');
+      $this->assertRaw('Broken teardown');
+      $this->assertNoRaw('The tearDown() method has run.');
 
-      // Verify that the broken test and its tearDown() method are skipped.
-      $this->assertRaw(t('The test setUp() method has been run.'));
-      $this->assertRaw(t('The test cannot be executed because it has not been set up properly.'));
-      $this->assertNoRaw(t('The test method has run.'));
-      $this->assertNoRaw(t('The tearDown() method has run.'));
+      // Verify that a broken test method is caught.
+      file_put_contents($this->originalFileDirectory . '/simpletest/trigger', 'test');
+      $edit['Drupal\simpletest\Tests\BrokenSetUpTest'] = TRUE;
+      $this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests'));
+      $this->assertNoRaw('Broken setup');
+      $this->assertRaw('The setUp() method has run.');
+      $this->assertRaw('Broken test');
+      $this->assertNoRaw('The test method has run.');
+      $this->assertNoRaw('Broken teardown');
+      $this->assertRaw('The tearDown() method has run.');
     }
     // If the test is being run from within simpletest, output a message.
     else {
-      $this->pass(t('The test method has run.'));
+      if (file_get_contents($this->originalFileDirectory . '/simpletest/trigger') === 'test') {
+        throw new \Exception('Broken test');
+      }
+      $this->pass('The test method has run.');
     }
   }
 }
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php
index 5510aa4..86a7254 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php
@@ -41,32 +41,6 @@ function __construct($test_id = NULL) {
    * setUp() method.
    */
   protected function setUp() {
-    global $conf;
-
-    // Create the database prefix for this test.
-    $this->prepareDatabasePrefix();
-
-    // Prepare the environment for running tests.
-    $this->prepareEnvironment();
-    if (!$this->setupEnvironment) {
-      return FALSE;
-    }
-
-    // Reset all statics and variables to perform tests in a clean environment.
-    $conf = array();
-    drupal_static_reset();
-
     $this->settingsSet('file_public_path', $this->public_files_directory);
-
-    // Change the database prefix.
-    // All static variables need to be reset before the database prefix is
-    // changed, since \Drupal\Core\Utility\CacheArray implementations attempt to
-    // write back to persistent caches when they are destructed.
-    $this->changeDatabasePrefix();
-    if (!$this->setupDatabasePrefix) {
-      return FALSE;
-    }
-
-    $this->setup = TRUE;
   }
 }
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index 265012e..9d17cac 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -698,27 +698,19 @@ protected function drupalLogout() {
   /**
    * Sets up a Drupal site for running functional and integration tests.
    *
-   * Generates a random database prefix and installs Drupal with the specified
-   * installation profile in \Drupal\simpletest\WebTestBase::$profile into the
-   * prefixed database. Afterwards, installs any additional modules specified by
-   * the test.
+   * Installs Drupal with the installation profile specified in
+   * \Drupal\simpletest\WebTestBase::$profile into the prefixed database.
+
+   * Afterwards, installs any additional modules specified in the static
+   * \Drupal\simpletest\WebTestBase::$modules property of each class in the
+   * class hierarchy.
    *
    * After installation all caches are flushed and several configuration values
    * are reset to the values of the parent site executing the test, since the
    * default values may be incompatible with the environment in which tests are
    * being executed.
-   *
-   * @param ...
-   *   List of modules to enable for the duration of the test. This can be
-   *   either a single array or a variable number of string arguments.
-   *
-   * @see \Drupal\simpletest\WebTestBase::prepareDatabasePrefix()
-   * @see \Drupal\simpletest\WebTestBase::changeDatabasePrefix()
-   * @see \Drupal\simpletest\WebTestBase::prepareEnvironment()
    */
   protected function setUp() {
-    global $conf;
-
     // When running tests through the Simpletest UI (vs. on the command line),
     // Simpletest's batch conflicts with the installer's batch. Batch API does
     // not support the concept of nested batches (in which the nested is not
@@ -726,33 +718,6 @@ protected function setUp() {
     // Backup the currently running Simpletest batch.
     $this->originalBatch = batch_get();
 
-    // Create the database prefix for this test.
-    $this->prepareDatabasePrefix();
-
-    // Prepare the environment for running tests.
-    $this->prepareEnvironment();
-    if (!$this->setupEnvironment) {
-      return FALSE;
-    }
-
-    // Reset all statics and variables to perform tests in a clean environment.
-    $conf = array();
-    drupal_static_reset();
-
-    // Change the database prefix.
-    // All static variables need to be reset before the database prefix is
-    // changed, since \Drupal\Core\Utility\CacheArray implementations attempt to
-    // write back to persistent caches when they are destructed.
-    $this->changeDatabasePrefix();
-    if (!$this->setupDatabasePrefix) {
-      return FALSE;
-    }
-
-    // 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()
-    $conf['simpletest_parent_profile'] = $this->originalProfile;
-
     // Define information about the user 1 account.
     $this->root_user = new UserSession(array(
       'uid' => 1,
@@ -846,14 +811,12 @@ protected function setUp() {
     // Use the test mail class instead of the default mail handler class.
     \Drupal::config('system.mail')->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')->save();
 
-    drupal_set_time_limit($this->timeLimit);
     // Temporary fix so that when running from run-tests.sh we don't get an
     // empty current path which would indicate we're on the home page.
     $path = current_path();
     if (empty($path)) {
       _current_path('run-tests');
     }
-    $this->setup = TRUE;
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php b/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php
index 69b3352..f87fdc5 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php
@@ -15,6 +15,13 @@
  */
 class InstallerTranslationTest extends InstallerTest {
 
+  /**
+   * Whether the installer has completed.
+   *
+   * @var bool
+   */
+  protected $isInstalled = FALSE;
+
   public static function getInfo() {
     return array(
       'name' => 'Installer translation test',
@@ -24,41 +31,8 @@ public static function getInfo() {
   }
 
   protected function setUp() {
-    global $conf;
-
-    // When running tests through the SimpleTest UI (vs. on the command line),
-    // SimpleTest's batch conflicts with the installer's batch. Batch API does
-    // not support the concept of nested batches (in which the nested is not
-    // progressive), so we need to temporarily pretend there was no batch.
-    // Back up the currently running SimpleTest batch.
-    $this->originalBatch = batch_get();
-
-    // Add the translations directory so we can retrieve German translations.
-    $conf['locale.settings']['translation.path'] = drupal_get_path('module', 'simpletest') . '/files/translations';
-    $conf['language_default']['name'] = 'German';
-    $conf['language_default']['id'] = 'de';
-
-    // Create the database prefix for this test.
-    $this->prepareDatabasePrefix();
-
-    // Prepare the environment for running tests.
-    $this->prepareEnvironment();
-    if (!$this->setupEnvironment) {
-      return FALSE;
-    }
+    $this->isInstalled = FALSE;
 
-    // Reset all statics and variables to perform tests in a clean environment.
-    $conf = array();
-    drupal_static_reset();
-
-    // Change the database prefix.
-    // All static variables need to be reset before the database prefix is
-    // changed, since \Drupal\Core\Utility\CacheArray implementations attempt to
-    // write back to persistent caches when they are destructed.
-    $this->changeDatabasePrefix();
-    if (!$this->setupDatabasePrefix) {
-      return FALSE;
-    }
     $variable_groups = array(
       'system.file' => array(
         'path.private' =>  $this->private_files_directory,
@@ -134,14 +108,14 @@ protected function setUp() {
     // Use the test mail class instead of the default mail handler class.
     \Drupal::config('system.mail')->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')->save();
 
-    drupal_set_time_limit($this->timeLimit);
     // When running from run-tests.sh we don't get an empty current path which
     // would indicate we're on the home page.
     $path = current_path();
     if (empty($path)) {
       _current_path('run-tests');
     }
-    $this->setup = TRUE;
+
+    $this->isInstalled = TRUE;
   }
 
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php b/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
index 5bbaab8..d4f1c5c 100644
--- a/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
@@ -15,6 +15,13 @@
  */
 class InstallerTest extends WebTestBase {
 
+  /**
+   * Whether the installer has completed.
+   *
+   * @var bool
+   */
+  protected $isInstalled = FALSE;
+
   public static function getInfo() {
     return array(
       'name' => 'Installer tests',
@@ -24,36 +31,8 @@ public static function getInfo() {
   }
 
   protected function setUp() {
-    global $conf;
-
-    // When running tests through the SimpleTest UI (vs. on the command line),
-    // SimpleTest's batch conflicts with the installer's batch. Batch API does
-    // not support the concept of nested batches (in which the nested is not
-    // progressive), so we need to temporarily pretend there was no batch.
-    // Back up the currently running SimpleTest batch.
-    $this->originalBatch = batch_get();
-
-    // Create the database prefix for this test.
-    $this->prepareDatabasePrefix();
-
-    // Prepare the environment for running tests.
-    $this->prepareEnvironment();
-    if (!$this->setupEnvironment) {
-      return FALSE;
-    }
+    $this->isInstalled = FALSE;
 
-    // Reset all statics and variables to perform tests in a clean environment.
-    $conf = array();
-    drupal_static_reset();
-
-    // Change the database prefix.
-    // All static variables need to be reset before the database prefix is
-    // changed, since \Drupal\Core\Utility\CacheArray implementations attempt to
-    // write back to persistent caches when they are destructed.
-    $this->changeDatabasePrefix();
-    if (!$this->setupDatabasePrefix) {
-      return FALSE;
-    }
     $variable_groups = array(
       'system.file' => array(
         'path.private' =>  $this->private_files_directory,
@@ -98,25 +77,24 @@ protected function setUp() {
     // Use the test mail class instead of the default mail handler class.
     \Drupal::config('system.mail')->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')->save();
 
-    drupal_set_time_limit($this->timeLimit);
     // When running from run-tests.sh we don't get an empty current path which
     // would indicate we're on the home page.
     $path = current_path();
     if (empty($path)) {
       _current_path('run-tests');
     }
-    $this->setup = TRUE;
+
+    $this->isInstalled = TRUE;
   }
 
   /**
    * {@inheritdoc}
    *
-   * During setup(), drupalPost calls refreshVariables() which tries to read
-   * variables which are not yet there because the child Drupal is not yet
-   * installed.
+   * WebTestBase::refreshVariables() tries to operate on persistent storage,
+   * which is only available after the installer completed.
    */
   protected function refreshVariables() {
-    if (!empty($this->setup)) {
+    if ($this->isInstalled) {
       parent::refreshVariables();
     }
   }
