diff --git a/core/modules/simpletest/src/Exception/TestInWrongNamespaceException.php b/core/modules/simpletest/src/Exception/TestInWrongNamespaceException.php
new file mode 100644
index 0000000..60f1a41
--- /dev/null
+++ b/core/modules/simpletest/src/Exception/TestInWrongNamespaceException.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Drupal\simpletest\Exception;
+
+/**
+ * Exception thrown when a test class' namespace does not match a test type.
+ */
+class TestInWrongNamespaceException extends \LogicException {
+}
diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php
index d3a72dd..05c81ef 100644
--- a/core/modules/simpletest/src/TestDiscovery.php
+++ b/core/modules/simpletest/src/TestDiscovery.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Extension\ExtensionDiscovery;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\simpletest\Exception\MissingGroupException;
+use Drupal\simpletest\Exception\TestInWrongNamespaceException;
 use PHPUnit_Util_Test;
 
 /**
@@ -81,11 +82,13 @@ public function __construct($root, $class_loader, ModuleHandlerInterface $module
   }
 
   /**
-   * Registers test namespaces of all available extensions.
+   * Registers the test namespaces of all available extensions.
    *
    * @return array
    *   An associative array whose keys are PSR-4 namespace prefixes and whose
    *   values are directory names.
+   *
+   * @see https://www.drupal.org/node/2116043
    */
   public function registerTestNamespaces() {
     if (isset($this->testNamespaces)) {
@@ -107,18 +110,16 @@ public function registerTestNamespaces() {
 
       $base_path = $this->root . '/' . $extension->getPath();
 
-      // Add namespace of disabled/uninstalled extensions.
+      // Register namespace of disabled/uninstalled extensions on the
+      // classloader.
       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";
-      $this->testNamespaces["Drupal\\Tests\\$name\\FunctionalJavascript\\"][] = "$base_path/tests/src/FunctionalJavascript";
+      // Add PHPUnit-based test namespaces.
+      $this->testNamespaces["Drupal\\Tests\\$name\\"][] = "$base_path/tests/src";
     }
 
     foreach ($this->testNamespaces as $prefix => $paths) {
@@ -258,7 +259,7 @@ public function findAllClassFiles($extension = NULL) {
    * @param string $namespace_prefix
    *   The namespace prefix to use for discovered classes. Must contain a
    *   trailing namespace separator (backslash).
-   *   For example: 'Drupal\\node\\Tests\\'
+   *   For example: 'Drupal\\Tests\\node\\'
    * @param string $path
    *   The directory path to scan.
    *   For example: '/path/to/drupal/core/modules/node/tests/src'
@@ -351,12 +352,10 @@ public static function getTestInfo($classname, $doc_comment = NULL) {
       throw new MissingGroupException(sprintf('Missing @group annotation in %s', $classname));
     }
     $info['group'] = $annotations['group'];
-    // Put PHPUnit test suites into their own custom groups.
-    if ($testsuite = static::getPhpunitTestSuite($classname)) {
-      $info['type'] = 'PHPUnit-' . $testsuite;
-    }
-    else {
-      $info['type'] = 'Simpletest';
+
+    // Set the type to the test suite.
+    if ($testsuite = static::getTestSuite($classname)) {
+      $info['type'] = $testsuite;
     }
 
     if (!empty($annotations['coversDefaultClass'])) {
@@ -437,13 +436,48 @@ public static function parseTestClassAnnotations(\ReflectionClass $class) {
   }
 
   /**
+   * Determines the test suite for a given class name, based on class name.
+   *
+   * A test suite is any special case for running the given test class. This
+   * includes both PHPUnit-* variants and Simpletest.
+   *
+   * @param string $classname
+   *   The class name.
+   *
+   * @return string
+   *   The test suite. This could be any of the PHPUnit-* variants or
+   *   Simpletest.
+   *
+   * @throws \Drupal\simpletest\Exception\TestInWrongNamespaceException
+   *   Thrown when the test class' namespace does not reflect a known test
+   *   suite.
+   */
+  public static function getTestSuite($classname) {
+    if ($suite = static::getPhpunitTestSuite($classname)) {
+      return 'PHPUnit-' . $suite;
+    }
+    else {
+      // Looking for '\Drupal\module\Tests\AnyTestName\AnyLength' with or
+      // without leading '\'.
+      if (preg_match('/\\\\?Drupal\\\\[a-z]\\w*\\\\Tests\\\\[a-zA-Z\\\\]*/', $classname) == 1) {
+        return 'Simpletest';
+      }
+    }
+    throw new TestInWrongNamespaceException(' -- Test class ' . $classname . ' cannot be assigned to a test suite. See https://www.drupal.org/phpunit. -- ');
+  }
+
+  /**
    * Determines the phpunit testsuite for a given classname.
    *
+   * The current PHPUnit test suites are: Unit, Kernel, Functional, and
+   * FunctionalJavascript.
+   *
    * @param string $classname
    *   The test classname.
    *
    * @return string|false
-   *   The testsuite name or FALSE if its not a phpunit test.
+   *   The testsuite name or FALSE if the class name does not reflect one of
+   *   Unit, Kernel, Functional, or FunctionalJavascript.
    */
   public static function getPhpunitTestSuite($classname) {
     if (preg_match('/Drupal\\\\Tests\\\\Core\\\\(\w+)/', $classname, $matches)) {
@@ -453,15 +487,20 @@ public static function getPhpunitTestSuite($classname) {
       return 'Unit';
     }
     // Module tests.
+    $suites = ['Unit', 'Kernel', 'Functional', 'FunctionalJavascript'];
     if (preg_match('/Drupal\\\\Tests\\\\(\w+)\\\\(\w+)/', $classname, $matches)) {
-      return $matches[2];
+      if (in_array($matches[2], $suites)) {
+        return $matches[2];
+      }
     }
     // Core tests.
     elseif (preg_match('/Drupal\\\\(\w*)Tests\\\\/', $classname, $matches)) {
       if ($matches[1] == '') {
         return 'Unit';
       }
-      return $matches[1];
+      if (in_array($matches[1], $suites)) {
+        return $matches[1];
+      }
     }
     return FALSE;
   }
diff --git a/core/modules/simpletest/tests/src/Kernel/TestDiscoveryTest.php b/core/modules/simpletest/tests/src/Kernel/TestDiscoveryTest.php
new file mode 100644
index 0000000..33c1c2f
--- /dev/null
+++ b/core/modules/simpletest/tests/src/Kernel/TestDiscoveryTest.php
@@ -0,0 +1,286 @@
+<?php
+
+namespace Drupal\Tests\simpletest\Kernel;
+
+use Composer\Autoload\ClassLoader;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\simpletest\Exception\TestInWrongNamespaceException;
+use Drupal\simpletest\TestDiscovery;
+use Drupal\KernelTests\KernelTestBase;
+use org\bovigo\vfs\vfsStream;
+
+/**
+ * Tests the test discovery mechanisms.
+ *
+ * @group simpletest
+ *
+ * @coversDefaultClass Drupal\simpletest\TestDiscovery
+ *
+ * @see \Drupal\Tests\simpletest\Unit\TestInfoParsingTest
+ *
+ * @todo: Change this to a Unit test when ExtensionDiscovery does not require
+ *   services or the kernel.
+ */
+class TestDiscoveryTest extends KernelTestBase {
+
+  /**
+   * Build a fixture file system with a module with tests.
+   */
+  protected function setupVfsWithTestClasses() {
+    vfsStream::setup('drupal');
+
+    $test_file = <<<EOF
+<?php
+
+/**
+ * Test description
+ * @group example
+ */
+class FunctionalExampleTest {}
+EOF;
+
+    vfsStream::create([
+      'modules' => [
+        'test_module' => [
+          'test_module.info.yml' => 'type: module',
+          'tests' => [
+            'src' => [
+              'Functional' => [
+                'FunctionalExampleTest.php' => $test_file,
+                'FunctionalExampleTest2.php' => str_replace(
+                  ['FunctionalExampleTest', '@group example'],
+                  ['FunctionalExampleTest2', '@group example2'],
+                  $test_file
+                ),
+              ],
+              'Kernel' => [
+                'KernelExampleTest3.php' => str_replace(
+                  ['FunctionalExampleTest', '@group example'],
+                  ['KernelExampleTest3', '@group example2'],
+                  $test_file
+                ),
+              ],
+            ],
+          ],
+        ],
+      ],
+    ]);
+  }
+
+  /**
+   * @covers ::registerTestNamespaces
+   */
+  public function testRegisterTestNamespaces() {
+    $this->setupVfsWithTestClasses();
+    $class_loader = new ClassLoader();
+    $module_handler = $this->prophesize(ModuleHandlerInterface::class);
+
+    $test_discovery = new TestDiscovery('vfs://drupal', $class_loader, $module_handler->reveal());
+
+    $discovery_psr4 = $test_discovery->registerTestNamespaces();
+    $classloader_psr4 = $class_loader->getPrefixesPsr4();
+
+    // The module's src/ namespace is not in the test namespace, but is added
+    // to the classloader if the module is not 'enabled'.
+    $src_namespace = 'Drupal\\test_module\\';
+    $this->assertArrayHasKey($src_namespace, $classloader_psr4);
+    $this->assertFalse(isset($discovery_psr4[$src_namespace]));
+
+    $test_namespaces = [
+      'Drupal\\test_module\\Tests\\',
+      'Drupal\\Tests\\test_module\\',
+    ];
+    foreach ($test_namespaces as $test_namespace) {
+      $this->assertArrayHasKey($test_namespace, $discovery_psr4);
+    }
+  }
+
+  /**
+   * @covers ::getTestClasses
+   */
+  public function testGetTestClasses() {
+    $this->setupVfsWithTestClasses();
+
+    $class_loader = new ClassLoader();
+    $module_handler = $this->prophesize(ModuleHandlerInterface::class);
+
+    $test_discovery = new TestDiscovery('vfs://drupal', $class_loader, $module_handler->reveal());
+
+    $classes = $test_discovery->getTestClasses();
+
+    // Groups we expect to find at the top level of the classes response.
+    $groups = [
+      'example',
+      'example2',
+    ];
+    $ungrouped_classes = [];
+    foreach ($groups as $group) {
+      $this->assertArrayHasKey($group, $classes);
+      // Build the array for the next test.
+      $ungrouped_classes = array_merge($ungrouped_classes, $classes[$group]);
+    }
+    // Make sure we got all of the groups.
+    $this->assertEquals(count($groups), count(array_keys($classes)));
+
+    // Class names we expect to find.
+    $expected_classes = [
+      'Drupal\Tests\test_module\Functional\FunctionalExampleTest',
+      'Drupal\Tests\test_module\Functional\FunctionalExampleTest2',
+      'Drupal\Tests\test_module\Kernel\KernelExampleTest3',
+    ];
+    foreach ($expected_classes as $expected_class) {
+      $this->assertArrayHasKey($expected_class, $ungrouped_classes);
+    }
+    // Make sure we got all of the classes.
+    $this->assertEquals(count($expected_classes), count($ungrouped_classes));
+  }
+
+  /**
+   * Build a fixture file system with a module with tests.
+   */
+  protected function setupVfsTestWithWrongNamespace() {
+    vfsStream::setup('drupal');
+
+    $test_file = <<<EOF
+<?php
+
+/**
+ * Test description
+ * @group example
+ */
+class FunctionalExampleTest {}
+EOF;
+
+    vfsStream::create([
+      'modules' => [
+        'test_module' => [
+          'test_module.info.yml' => 'type: module',
+          'tests' => [
+            'src' => [
+              'Unfunctional' => [
+                'FunctionalExampleTest.php' => $test_file,
+              ],
+            ],
+          ],
+        ],
+      ],
+    ]);
+  }
+
+  /**
+   * @covers ::getTestClasses
+   */
+  public function testGetTestClassesWrongNamespace() {
+    $this->setExpectedException(TestInWrongNamespaceException::class);
+
+    $this->setupVfsTestWithWrongNamespace();
+    $class_loader = new ClassLoader();
+    $module_handler = $this->prophesize(ModuleHandlerInterface::class);
+
+    $test_discovery = new TestDiscovery('vfs://drupal', $class_loader, $module_handler->reveal());
+
+    $classes = $test_discovery->getTestClasses();
+  }
+
+  /**
+   * @covers ::getPhpunitTestSuite
+   * @dataProvider providerTestGetPhpunitTestSuite
+   */
+  public function testGetPhpunitTestSuite($classname, $expected, $suite) {
+    $this->assertEquals($expected, TestDiscovery::getPhpunitTestSuite($classname));
+  }
+
+  /**
+   * @covers ::getTestSuite
+   * @dataProvider providerTestGetPhpunitTestSuite
+   */
+  public function testGetTestSuite($classname, $type, $expected) {
+    $this->assertEquals($expected, TestDiscovery::getTestSuite($classname));
+  }
+
+  /**
+   * @return array[]
+   *   - Class name.
+   *   - PHPUnit-based test suite, or FALSE.
+   *   - Test suite.
+   */
+  public function providerTestGetPhpunitTestSuite() {
+    $data = [];
+    $data['simpletest-webtest'] = [
+      '\Drupal\rest\Tests\NodeTest',
+      FALSE,
+      'Simpletest',
+    ];
+    $data['simpletest-kerneltest'] = [
+      '\Drupal\hal\Tests\FileNormalizeTest',
+      FALSE,
+      'Simpletest',
+    ];
+    $data['module-unittest'] = [
+      'Drupal\Tests\simpletest\Unit\PhpUnitAutoloaderTest',
+      'Unit',
+      'PHPUnit-Unit',
+    ];
+    $data['module-kerneltest'] = [
+      static::class,
+      'Kernel',
+      'PHPUnit-Kernel',
+    ];
+    $data['module-functionaltest'] = [
+      '\Drupal\Tests\simpletest\Functional\BrowserTestBaseTest',
+      'Functional',
+      'PHPUnit-Functional',
+    ];
+    $data['module-functionaljavascripttest'] = [
+      '\Drupal\Tests\toolbar\FunctionalJavascript\ToolbarIntegrationTest',
+      'FunctionalJavascript',
+      'PHPUnit-FunctionalJavascript'
+    ];
+    $data['core-unittest'] = [
+      '\Drupal\Tests\ComposerIntegrationTest',
+      'Unit',
+      'PHPUnit-Unit',
+    ];
+    $data['core-unittest2'] = [
+      'Drupal\Tests\Core\DrupalTest',
+      'Unit',
+      'PHPUnit-Unit',
+    ];
+    $data['core-kerneltest'] = [
+      '\Drupal\KernelTests\KernelTestBaseTest',
+      'Kernel',
+      'PHPUnit-Kernel',
+    ];
+    $data['core-functionaltest'] = [
+      '\Drupal\FunctionalTests\ExampleTest',
+      'Functional',
+      'PHPUnit-Functional',
+    ];
+    $data['core-functionaljavascripttest'] = [
+      '\Drupal\FunctionalJavascriptTests\ExampleTest',
+      'FunctionalJavascript',
+      'PHPUnit-FunctionalJavascript',
+    ];
+    return $data;
+  }
+
+  /**
+   * @dataProvider provideGetTestSuiteBadSuite
+   */
+  public function testGetTestSuiteBadSuite($classname) {
+    $this->setExpectedException(TestInWrongNamespaceException::class);
+    TestDiscovery::getTestSuite($classname);
+  }
+
+  public function provideGetTestSuiteBadSuite() {
+    return [
+      ['\Boy\Is\This\Ever\Wrong'],
+      ['\Drupal\Tests\module\kernel\Test'],
+      ['\Drupal\Tests\module\functional\Test'],
+      ['\Drupal\Tests\module\functionalJavascript\Test'],
+      ['\Drupal\Tests\module\unit\Test'],
+      ['\Drupal\Tests\module\Unfunctional\Test'],
+    ];
+  }
+
+}
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..ebd5da7
--- /dev/null
+++ b/core/modules/simpletest/tests/src/Traits/TestTrait.php
@@ -0,0 +1,31 @@
+<?php
+
+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/TestInfoParsingTest.php b/core/modules/simpletest/tests/src/Unit/TestInfoParsingTest.php
index 9a69d78..096e7aa 100644
--- a/core/modules/simpletest/tests/src/Unit/TestInfoParsingTest.php
+++ b/core/modules/simpletest/tests/src/Unit/TestInfoParsingTest.php
@@ -17,6 +17,8 @@
 /**
  * @coversDefaultClass \Drupal\simpletest\TestDiscovery
  * @group simpletest
+ *
+ * @see \Drupal\simpletest\Unit\TestDiscoveryTest
  */
 class TestInfoParsingTest extends UnitTestCase {
 
@@ -386,31 +388,6 @@ protected function getExtensions() {
     return $this->extensions;
   }
 
-  /**
-   * @covers ::getPhpunitTestSuite
-   * @dataProvider providerTestGetPhpunitTestSuite
-   */
-  public function testGetPhpunitTestSuite($classname, $expected) {
-    $this->assertEquals($expected, TestDiscovery::getPhpunitTestSuite($classname));
-  }
-
-  public function providerTestGetPhpunitTestSuite() {
-    $data = [];
-    $data['simpletest-webtest'] = ['\Drupal\rest\Tests\NodeTest', FALSE];
-    $data['simpletest-kerneltest'] = ['\Drupal\hal\Tests\FileNormalizeTest', FALSE];
-    $data['module-unittest'] = [static::class, 'Unit'];
-    $data['module-kerneltest'] = ['\Drupal\KernelTests\Core\Theme\TwigMarkupInterfaceTest', 'Kernel'];
-    $data['module-functionaltest'] = ['\Drupal\Tests\simpletest\Functional\BrowserTestBaseTest', 'Functional'];
-    $data['module-functionaljavascripttest'] = ['\Drupal\Tests\toolbar\FunctionalJavascript\ToolbarIntegrationTest', 'FunctionalJavascript'];
-    $data['core-unittest'] = ['\Drupal\Tests\ComposerIntegrationTest', 'Unit'];
-    $data['core-unittest2'] = ['Drupal\Tests\Core\DrupalTest', 'Unit'];
-    $data['core-kerneltest'] = ['\Drupal\KernelTests\KernelTestBaseTest', 'Kernel'];
-    $data['core-functionaltest'] = ['\Drupal\FunctionalTests\ExampleTest', 'Functional'];
-    $data['core-functionaljavascripttest'] = ['\Drupal\FunctionalJavascriptTests\ExampleTest', 'FunctionalJavascript'];
-
-    return $data;
-  }
-
 }
 
 }
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..0ec450b
--- /dev/null
+++ b/core/modules/simpletest/tests/src/Unit/TraitAccessTest.php
@@ -0,0 +1,24 @@
+<?php
+
+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() {
+    $this->assertSame('stuff', $this->getStuff());
+  }
+
+}
