diff --git a/core/modules/migrate/tests/src/Unit/Entity/MigrationTest.php b/core/modules/migrate/tests/src/Unit/Entity/MigrationTest.php
index 027cccd..73ab716 100644
--- a/core/modules/migrate/tests/src/Unit/Entity/MigrationTest.php
+++ b/core/modules/migrate/tests/src/Unit/Entity/MigrationTest.php
@@ -14,6 +14,7 @@
  * Tests the migrate entity.
  *
  * @coversDefaultClass \Drupal\migrate\Entity\Migration
+ * @group migrate
  */
 class MigrationTest extends UnitTestCase {
 
diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php
index 50e19f0..f09bb14 100644
--- a/core/modules/simpletest/src/TestDiscovery.php
+++ b/core/modules/simpletest/src/TestDiscovery.php
@@ -8,6 +8,11 @@
 namespace Drupal\simpletest;
 
 use Composer\Autoload\ClassLoader;
+use Doctrine\Common\Annotations\AnnotationRegistry;
+use Doctrine\Common\Annotations\SimpleAnnotationReader;
+use Doctrine\Common\Reflection\StaticReflectionParser;
+use Drupal\Component\Annotation\Reflection\MockFileFinder;
+use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\Extension;
 use Drupal\Core\Extension\ExtensionDiscovery;
@@ -128,6 +133,9 @@ public function registerTestNamespaces() {
    * @todo Add base class groups 'Kernel' + 'Web', complementing 'PHPUnit'.
    */
   public function getTestClasses($extension = NULL) {
+    $reader = new SimpleAnnotationReader();
+    $reader->addNamespace('Drupal\\simpletest\\Annotation');
+
     if (!isset($extension)) {
       if ($this->cacheBackend && $cache = $this->cacheBackend->get('simpletest:discovery:classes')) {
         return $cache->data;
@@ -144,24 +152,43 @@ public function getTestClasses($extension = NULL) {
     $this->classLoader->addClassMap($classmap);
 
     foreach ($classmap as $classname => $pathname) {
-      try {
-        $class = new \ReflectionClass($classname);
+      $finder = MockFileFinder::create($pathname);
+      $parser = new StaticReflectionParser($classname, $finder, TRUE);
+      $doc_comment = $parser->getDocComment();
+      $info = array(
+        'name' => $classname,
+      );
+      $annotations = array();
+      preg_match_all('/^ \* \@([^\s]*) (.*$)/m', $doc_comment, $matches);
+      if (isset($matches[1])) {
+        foreach ($matches[1] as $key => $annotation) {
+          if (!empty($annotations[$annotation])) {
+            // Only have the first match per annotation. This deals with
+            // multiple @group annotations.
+            continue;
+          }
+          $annotations[$annotation] = $matches[2][$key];
+        }
       }
-      catch (\ReflectionException $e) {
-        // Re-throw with expected pathname.
-        $message = $e->getMessage() . " in expected $pathname";
-        throw new \ReflectionException($message, $e->getCode(), $e);
+      if (!empty($annotations['coversDefaultClass'])) {
+        $info['description'] = 'Tests ' . $annotations['coversDefaultClass'] . '.';
       }
-      // Skip interfaces, abstract classes, and traits.
-      if (!$class->isInstantiable()) {
-        continue;
+      else {
+        // Some preg to strip annotations and comment headers.
+        preg_match('/^ \* (.*)$/m', $doc_comment, $matches);
+        $info['description'] = isset($matches[1]) ? $matches[1] : 'No description.';
+      }
+      if (isset($annotations['dependencies'])) {
+        $info['requires']['module'] = explode(',', $annotations['dependencies']);
       }
-      // Skip non-test classes.
-      if (!$class->isSubclassOf('Drupal\simpletest\TestBase') && !$class->isSubclassOf('PHPUnit_Framework_TestCase')) {
+
+      if (empty($annotations['group'])) {
+        // Concrete tests must have a group and abstract and
         continue;
       }
-      $info = static::getTestInfo($class);
 
+      // Standardise the group name.
+      $info['group'] = Unicode::ucfirst($annotations['group']);
       // 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
@@ -311,42 +338,13 @@ public static function getTestInfo(\ReflectionClass $class) {
       throw new \LogicException(sprintf('Missing PHPDoc summary line on %s in %s.', $classname, $class->getFileName()));
     }
 
+    if (isset($annotations['dependencies'])) {
+      $info['requires']['module'] = array_merge($info['requires']['module'], explode(',', $annotations['dependencies']));
+    }
+
     // Reduce to @group and @requires.
     $info += array_intersect_key($annotations, array('group' => 1, 'requires' => 1));
 
-    // @todo Remove legacy getInfo() methods.
-    if (method_exists($classname, 'getInfo')) {
-      $legacy_info = $classname::getInfo();
-
-      // Derive the primary @group from the namespace to ensure that legacy
-      // tests are not located in different groups than converted tests.
-      $classparts = explode('\\', $classname);
-      if ($classparts[1] === 'Tests') {
-        if ($classparts[2] === 'Component' || $classparts[2] === 'Core') {
-          // Drupal\Tests\Component\{group}\...
-          $info['group'][] = $classparts[3];
-        }
-        else {
-          // Drupal\Tests\{group}\...
-          $info['group'][] = $classparts[2];
-        }
-      }
-      elseif ($classparts[1] === 'system' && $classparts[3] !== 'System') {
-        // Drupal\system\Tests\{group}\...
-        $info['group'][] = $classparts[3];
-      }
-      else {
-        // Drupal\{group}\Tests\...
-        $info['group'][] = $classparts[1];
-      }
-
-      if (isset($legacy_info['dependencies'])) {
-        $info += array('requires' => array());
-        $info['requires'] += array('module' => array());
-        $info['requires']['module'] = array_merge($info['requires']['module'], $legacy_info['dependencies']);
-      }
-    }
-
     // Process @group information.
     // @todo Support multiple @groups + change UI to expose a group select
     //   dropdown to filter tests by group instead of collapsible table rows.
diff --git a/core/modules/simpletest/src/Tests/MissingDependentModuleUnitTest.php b/core/modules/simpletest/src/Tests/MissingDependentModuleUnitTest.php
index 956f280..a9e375c 100644
--- a/core/modules/simpletest/src/Tests/MissingDependentModuleUnitTest.php
+++ b/core/modules/simpletest/src/Tests/MissingDependentModuleUnitTest.php
@@ -13,7 +13,7 @@
  * This test should not load since it requires a module that is not found.
  *
  * @group simpletest
- * @requires module simpletest_missing_module
+ * @dependencies simpletest_missing_module
  */
 class MissingDependentModuleUnitTest extends KernelTestBase {
 
diff --git a/core/tests/Drupal/Tests/Component/EventDispatcher/ContainerAwareEventDispatcherTest.php b/core/tests/Drupal/Tests/Component/EventDispatcher/ContainerAwareEventDispatcherTest.php
index b297626..58c11b0 100644
--- a/core/tests/Drupal/Tests/Component/EventDispatcher/ContainerAwareEventDispatcherTest.php
+++ b/core/tests/Drupal/Tests/Component/EventDispatcher/ContainerAwareEventDispatcherTest.php
@@ -26,6 +26,8 @@
  * synchronizations.
  *
  * @see https://github.com/symfony/symfony/pull/12521
+ *
+ * @group Symfony
  */
 class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest
 {
diff --git a/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php b/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php
index 21dcf7c..0854ebe 100644
--- a/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php
+++ b/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php
@@ -12,6 +12,7 @@
 
 /**
  * @coversDefaultClass \Drupal\Component\Plugin\DefaultFactory
+ * @group Plugin
  */
 class DefaultFactoryTest extends UnitTestCase {
 
