diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php
index 630cca7..dde088f 100644
--- a/core/modules/simpletest/src/TestDiscovery.php
+++ b/core/modules/simpletest/src/TestDiscovery.php
@@ -18,6 +18,21 @@
 
 /**
  * Discovers available tests.
+ *
+ * There are a number of types of tests supported by Drupal 8, and this
+ * discovery class. They are marked by their different namespaces.
+ *
+ * They are:
+ * - Drupal\Tests\
+ * - Drupal\Tests\module_name\Unit\
+ * - Drupal\Tests\module_name\Kernel\
+ * - Drupal\Tests\module_name\Functional\
+ *
+ * @todo Expand this documentation.
+ *
+ * @see testing
+ * @see core.api.php
+ * @see https://www.drupal.org/node/2116043
  */
 class TestDiscovery {
 
@@ -45,11 +60,20 @@ class TestDiscovery {
   /**
    * Cached list of all available extension names, keyed by extension type.
    *
-   * @var array
+   * @var string[]
    */
   protected $availableExtensions;
 
   /**
+   * Cached extension discovery object.
+   *
+   * ExtensionDiscovery caches its results, so we store it here for re-use.
+   *
+   * @var \Drupal\Core\Extension\ExtensionDiscovery
+   */
+  protected $extensionDiscovery;
+
+  /**
    * Constructs a new test discovery.
    *
    * @param $class_loader
@@ -65,20 +89,54 @@ public function __construct($class_loader, CacheBackendInterface $cache_backend
   }
 
   /**
-   * Registers test namespaces of all available extensions.
+   * Registers test namespaces to classloader, including disabled extensions.
    *
-   * @return array
+   * Test class discovery will find tests in disabled extensions, so we must
+   * also register their non-test namespaces.
+   *
+   * @return string[]
    *   An associative array whose keys are PSR-4 namespace prefixes and whose
-   *   values are directory names.
+   *   values are directory names. This array represents the PSR-4 namespaces
+   *   which were registered to the classloader by this method.
    */
   public function registerTestNamespaces() {
+    // Gather test-related namespaces.
+    $namespaces = $this->getTestNamespaces();
+    
+    // Add in namespaces for disabled extensions.
+    $existing_psr4 = $this->classLoader->getPrefixesPsr4();
+    foreach ($this->getExtensions() as $name => $extension) {
+      // Add namespace of disabled/uninstalled extensions.
+      if (!isset($existing_psr4["Drupal\\$name\\"])) {
+        $base_path = DRUPAL_ROOT . '/' . $extension->getPath();
+        $this->classLoader->addPsr4("Drupal\\$name\\", "$base_path/src");
+      }
+    }
+    // Perform the registration.
+    foreach ($namespaces as $prefix => $paths) {
+      $this->classLoader->addPsr4($prefix, $paths);
+    }
+    return $namespaces;
+  }
+
+  /**
+   * Get namespaces related to tests.
+   *
+   * Also populate availableExtensions.
+   *
+   * The tests we eventually run might require traits and interfaces from these
+   * namespaces, so we cast a wide net.
+   *
+   * @return string[]
+   *   An associative array whose keys are PSR-4 namespace prefixes and whose
+   *   values are directory names.
+   */
+  protected function getTestNamespaces() {
     if (isset($this->testNamespaces)) {
       return $this->testNamespaces;
     }
     $this->testNamespaces = array();
 
-    $existing = $this->classLoader->getPrefixesPsr4();
-
     // Add PHPUnit test namespaces of Drupal core.
     $this->testNamespaces['Drupal\\Tests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/Tests'];
     $this->testNamespaces['Drupal\\KernelTests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/KernelTests'];
@@ -90,21 +148,11 @@ public function registerTestNamespaces() {
 
       $base_path = DRUPAL_ROOT . '/' . $extension->getPath();
 
-      // Add namespace of disabled/uninstalled extensions.
-      if (!isset($existing["Drupal\\$name\\"])) {
-        $this->classLoader->addPsr4("Drupal\\$name\\", "$base_path/src");
-      }
       // Add Simpletest test namespace.
       $this->testNamespaces["Drupal\\$name\\Tests\\"][] = "$base_path/src/Tests";
 
-      // Add PHPUnit test namespaces.
-      $this->testNamespaces["Drupal\\Tests\\$name\\Unit\\"][] = "$base_path/tests/src/Unit";
-      $this->testNamespaces["Drupal\\Tests\\$name\\Kernel\\"][] = "$base_path/tests/src/Kernel";
-      $this->testNamespaces["Drupal\\Tests\\$name\\Functional\\"][] = "$base_path/tests/src/Functional";
-    }
-
-    foreach ($this->testNamespaces as $prefix => $paths) {
-      $this->classLoader->addPsr4($prefix, $paths);
+      // Add PHPUnit test namespace.
+      $this->testNamespaces["Drupal\\Tests\\$name\\"][] = "$base_path/tests/src";
     }
 
     return $this->testNamespaces;
@@ -131,6 +179,8 @@ public function registerTestNamespaces() {
    *
    * @throws \ReflectionException
    *   If a discovered test class does not match the expected class name.
+   * @throws \Drupal\simpletest\Exception\MissingGroupException
+   *   If a discovered test class does not specify a group in annotation.
    *
    * @todo Remove singular grouping; retain list of groups in 'group' key.
    * @see https://www.drupal.org/node/2296615
@@ -156,32 +206,34 @@ public function getTestClasses($extension = NULL) {
     $this->classLoader->addClassMap($classmap);
 
     foreach ($classmap as $classname => $pathname) {
-      $finder = MockFileFinder::create($pathname);
-      $parser = new StaticReflectionParser($classname, $finder, TRUE);
-      try {
-        $info = static::getTestInfo($classname, $parser->getDocComment());
-      }
-      catch (MissingGroupException $e) {
-        // If the class name ends in Test and is not a migrate table dump.
-        if (preg_match('/Test$/', $classname) && strpos($classname, 'migrate_drupal\Tests\Table') === FALSE) {
-          throw $e;
+      if ($this->isValidTestNamespace($classname)) {
+        $finder = MockFileFinder::create($pathname);
+        $parser = new StaticReflectionParser($classname, $finder, TRUE);
+        try {
+          $info = static::getTestInfo($classname, $parser->getDocComment());
         }
-        // If the class is @group annotation just skip it. Most likely it is an
-        // abstract class, trait or test fixture.
-        continue;
-      }
-      // Skip this test class if it requires unavailable modules.
-      // @todo PHPUnit skips tests with unmet requirements when executing a test
-      //   (instead of excluding them upfront). Refactor test runner to follow
-      //   that approach.
-      // @see https://www.drupal.org/node/1273478
-      if (!empty($info['requires']['module'])) {
-        if (array_diff($info['requires']['module'], $this->availableExtensions['module'])) {
+        catch (MissingGroupException $e) {
+          // If the class name ends in Test and is not a migrate table dump.
+          if (preg_match('/Test$/', $classname) && strpos($classname, 'migrate_drupal\Tests\Table') === FALSE) {
+            throw $e;
+          }
+          // If the class is @group annotation just skip it. Most likely it is an
+          // abstract class, trait or test fixture.
           continue;
         }
-      }
+        // Skip this test class if it requires unavailable modules.
+        // @todo PHPUnit skips tests with unmet requirements when executing a test
+        //   (instead of excluding them upfront). Refactor test runner to follow
+        //   that approach.
+        // @see https://www.drupal.org/node/1273478
+        if (!empty($info['requires']['module'])) {
+          if (array_diff($info['requires']['module'], $this->availableExtensions['module'])) {
+            continue;
+          }
+        }
 
-      $list[$info['group']][$classname] = $info;
+        $list[$info['group']][$classname] = $info;
+      }
     }
 
     // Sort the groups and tests within the groups by name.
@@ -202,12 +254,47 @@ public function getTestClasses($extension = NULL) {
   }
 
   /**
+   * Determine if the test class has a valid testable namespace.
+   *
+   * @param string @class_name
+   *   The class name.
+   *
+   * @returns bool
+   *   TRUE if the class name belongs to a proper namespace. FALSE otherwise.
+   */
+  protected function isValidTestNamespace($classname) {
+    // Basic check of whether we're in a Drupal namespace.
+    if (strpos($classname, 'Drupal\\') === 0) {
+      // Short-cut to check for PHPUnit-based tests.
+      if (static::isUnitTest($classname)) {
+        return TRUE;
+      }
+      $namespace = explode('\\', $classname);
+      if ($namespace[0] == 'Drupal') {
+        // Check for kernel tests.
+        if ($namespace[1] == 'KernelTests') {
+          return TRUE;
+        }
+        // Check if the test class belongs to an extension. Simpletest tests for
+        // core belong to the system module.
+        $extensions = array_keys($this->getExtensions());
+        if (in_array($namespace[1], $extensions)) {
+          if ($namespace[2] == 'Tests') {
+            return TRUE;
+          }
+        }
+      }
+    }
+    return FALSE;
+  }
+
+  /**
    * Discovers all class files in all available extensions.
    *
    * @param string $extension
    *   (optional) The name of an extension to limit discovery to; e.g., 'node'.
    *
-   * @return array
+   * @return string[]
    *   A classmap containing all discovered class files; i.e., a map of
    *   fully-qualified classnames to pathnames.
    */
@@ -430,7 +517,7 @@ public static function isUnitTest($classname) {
         // A core unit test.
         return TRUE;
       }
-      elseif ($namespace[3] == 'Unit') {
+      elseif (in_array($namespace[3], ['Unit', 'Kernel', 'Functional'])) {
         // A module unit test.
         return TRUE;
       }
@@ -445,12 +532,15 @@ public static function isUnitTest($classname) {
    *   An array of Extension objects, keyed by extension name.
    */
   protected function getExtensions() {
-    $listing = new ExtensionDiscovery(DRUPAL_ROOT);
-    // Ensure that tests in all profiles are discovered.
-    $listing->setProfileDirectories(array());
-    $extensions = $listing->scan('module', TRUE);
-    $extensions += $listing->scan('profile', TRUE);
-    $extensions += $listing->scan('theme', TRUE);
+    // ExtensionDiscovery caches its results, so we store it here.
+    if (empty($this->extensionDiscovery)) {
+      $this->extensionDiscovery = new ExtensionDiscovery(DRUPAL_ROOT);
+      // Ensure that tests in all profiles are discovered.
+      $this->extensionDiscovery->setProfileDirectories([]);
+    }
+    $extensions = $this->extensionDiscovery->scan('module', TRUE);
+    $extensions += $this->extensionDiscovery->scan('profile', TRUE);
+    $extensions += $this->extensionDiscovery->scan('theme', TRUE);
     return $extensions;
   }
 
diff --git a/core/modules/simpletest/tests/src/Traits/TestTrait.php b/core/modules/simpletest/tests/src/Traits/TestTrait.php
new file mode 100644
index 0000000..1bd1738
--- /dev/null
+++ b/core/modules/simpletest/tests/src/Traits/TestTrait.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\simpletest\TestTrait.
+ */
+
+namespace Drupal\Tests\simpletest\Traits;
+
+/**
+ * Provides a trait declared in the Drupal\Tests namespace.
+ *
+ * We declare the trait here to make sure that traits found in
+ * the Drupal\Tests namespace are visible to our test runner.
+ *
+ * @see Drupal\Tests\simpletest\Unit\TestTraitAccessTest
+ */
+trait TestTrait {
+  /**
+   * Test string for our not very interesting trait.
+   *
+   * @var string
+   */
+  protected $stuff = 'stuff';
+
+  /**
+   * Return a test string to a trait user.
+   *
+   * @return string
+   *   A test string.
+   */
+  protected function getStuff() {
+    return $this->stuff;
+  }
+
+}
diff --git a/core/modules/simpletest/tests/src/Unit/TraitAccessTest.php b/core/modules/simpletest/tests/src/Unit/TraitAccessTest.php
new file mode 100644
index 0000000..46fe07d
--- /dev/null
+++ b/core/modules/simpletest/tests/src/Unit/TraitAccessTest.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\Tests\simpletest\Unit\TraitAccessTest.
+ *
+ *  Demonstration of a very simple PHPUnit test using a trait.
+ */
+
+namespace Drupal\Tests\simpletest\Unit;
+
+use Drupal\Tests\UnitTestCase;
+use Drupal\Tests\simpletest\Traits\TestTrait;
+
+/**
+ * Tests if a PHPUnit test can find a trait under the Drupal\Tests namespace.
+ *
+ * @group simpletest
+ */
+class TraitAccessTest extends UnitTestCase {
+
+  use TestTrait;
+
+  /**
+   * Test if method acquired via the trait is found.
+   */
+  public function testSimpleStuff() {
+    $stuff = $this->getStuff();
+    $this->assertSame($stuff, 'stuff', "Same old stuff");
+  }
+
+}
