diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 8ae17bc46ce8f..2700da75a5b2a 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/config/features.gni")
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 gypi_values = exec_script("//build/gypi_to_gn.py",
                           [ rebase_path("ash.gyp") ],
diff --git a/base/BUILD.gn b/base/BUILD.gn
index db3cb4ac89dff..065f87174c687 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 if (is_android) {
   import("//build/config/android/rules.gni")
@@ -1377,6 +1378,13 @@ test("base_unittests") {
     "//third_party/icu",
   ]
 
+  if (is_android) {
+    apk_deps = [
+      ":base_java",
+      ":base_java_unittest_support",
+    ]
+  }
+
   if (is_ios) {
     sources -= [
       "metrics/stats_table_uinittest.cc",  # Requires spawning a process.
@@ -1531,14 +1539,4 @@ if (is_android) {
     java_files =
         [ "test/android/java/src/org/chromium/base/ContentUriTestUtils.java" ]
   }
-
-  # GYP: //base.gyp:base_unittests_apk
-  unittest_apk("base_unittests_apk") {
-    deps = [
-      ":base_java",
-      ":base_java_unittest_support",
-      ":base_unittests",
-    ]
-    unittests_dep = ":base_unittests"
-  }
 }
diff --git a/breakpad/BUILD.gn b/breakpad/BUILD.gn
index 190549a8ea5eb..c9280812808d3 100644
--- a/breakpad/BUILD.gn
+++ b/breakpad/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 config("tools_config") {
   include_dirs = [
     "src",
diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn
index e9e7006dc6487..099db5192dfba 100644
--- a/build/config/BUILDCONFIG.gn
+++ b/build/config/BUILDCONFIG.gn
@@ -717,202 +717,3 @@ template("component") {
     }
   }
 }
-
-# ==============================================================================
-# TEST SETUP
-# ==============================================================================
-
-# Define a test as an executable (or shared_library on Android) with the
-# "testonly" flag set.
-template("test") {
-  if (is_android) {
-    shared_library(target_name) {
-      # Configs will always be defined since we set_defaults for a component
-      # above. We want to use those rather than whatever came with the nested
-      # shared/static library inside the component.
-      configs = []  # Prevent list overwriting warning.
-      configs = invoker.configs
-
-      # See above call.
-      set_sources_assignment_filter([])
-
-      testonly = true
-
-      if (defined(invoker.all_dependent_configs)) {
-        all_dependent_configs = invoker.all_dependent_configs
-      }
-      if (defined(invoker.allow_circular_includes_from)) {
-        allow_circular_includes_from = invoker.allow_circular_includes_from
-      }
-      if (defined(invoker.cflags)) {
-        cflags = invoker.cflags
-      }
-      if (defined(invoker.cflags_c)) {
-        cflags_c = invoker.cflags_c
-      }
-      if (defined(invoker.cflags_cc)) {
-        cflags_cc = invoker.cflags_cc
-      }
-      if (defined(invoker.cflags_objc)) {
-        cflags_objc = invoker.cflags_objc
-      }
-      if (defined(invoker.cflags_objcc)) {
-        cflags_objcc = invoker.cflags_objcc
-      }
-      if (defined(invoker.check_includes)) {
-        check_includes = invoker.check_includes
-      }
-      if (defined(invoker.data)) {
-        data = invoker.data
-      }
-      if (defined(invoker.data_deps)) {
-        data_deps = invoker.data_deps
-      }
-      if (defined(invoker.datadeps)) {
-        datadeps = invoker.datadeps
-      }
-      if (defined(invoker.defines)) {
-        defines = invoker.defines
-      }
-      if (defined(invoker.deps)) {
-        deps = invoker.deps
-      }
-      if (defined(invoker.direct_dependent_configs)) {
-        direct_dependent_configs = invoker.direct_dependent_configs
-      }
-      if (defined(invoker.forward_dependent_configs_from)) {
-        forward_dependent_configs_from = invoker.forward_dependent_configs_from
-      }
-      if (defined(invoker.include_dirs)) {
-        include_dirs = invoker.include_dirs
-      }
-      if (defined(invoker.ldflags)) {
-        ldflags = invoker.ldflags
-      }
-      if (defined(invoker.lib_dirs)) {
-        lib_dirs = invoker.lib_dirs
-      }
-      if (defined(invoker.libs)) {
-        libs = invoker.libs
-      }
-      if (defined(invoker.output_extension)) {
-        output_extension = invoker.output_extension
-      }
-      if (defined(invoker.output_name)) {
-        output_name = invoker.output_name
-      }
-      if (defined(invoker.public)) {
-        public = invoker.public
-      }
-      if (defined(invoker.public_configs)) {
-        public_configs = invoker.public_configs
-      }
-      if (defined(invoker.public_deps)) {
-        public_deps = invoker.public_deps
-      }
-      if (defined(invoker.sources)) {
-        sources = invoker.sources
-      }
-      if (defined(invoker.visibility)) {
-        visibility = invoker.visibility
-      }
-    }
-  } else {
-    executable(target_name) {
-      # See above.
-      configs = []  # Prevent list overwriting warning.
-      configs = invoker.configs
-
-      # See above call.
-      set_sources_assignment_filter([])
-
-      testonly = true
-
-      if (defined(invoker.all_dependent_configs)) {
-        all_dependent_configs = invoker.all_dependent_configs
-      }
-      if (defined(invoker.allow_circular_includes_from)) {
-        allow_circular_includes_from = invoker.allow_circular_includes_from
-      }
-      if (defined(invoker.cflags)) {
-        cflags = invoker.cflags
-      }
-      if (defined(invoker.cflags_c)) {
-        cflags_c = invoker.cflags_c
-      }
-      if (defined(invoker.cflags_cc)) {
-        cflags_cc = invoker.cflags_cc
-      }
-      if (defined(invoker.cflags_objc)) {
-        cflags_objc = invoker.cflags_objc
-      }
-      if (defined(invoker.cflags_objcc)) {
-        cflags_objcc = invoker.cflags_objcc
-      }
-      if (defined(invoker.check_includes)) {
-        check_includes = invoker.check_includes
-      }
-      if (defined(invoker.data)) {
-        data = invoker.data
-      }
-      if (defined(invoker.data_deps)) {
-        data_deps = invoker.data_deps
-      }
-      if (defined(invoker.datadeps)) {
-        datadeps = invoker.datadeps
-      }
-      if (defined(invoker.defines)) {
-        defines = invoker.defines
-      }
-
-      # All shared libraries must have the sanitizer deps to properly link in
-      # asan mode (this target will be empty in other cases).
-      if (defined(invoker.deps)) {
-        deps = invoker.deps + [ "//build/config/sanitizers:deps" ]
-      } else {
-        deps = [
-          "//build/config/sanitizers:deps",
-        ]
-      }
-      if (defined(invoker.direct_dependent_configs)) {
-        direct_dependent_configs = invoker.direct_dependent_configs
-      }
-      if (defined(invoker.forward_dependent_configs_from)) {
-        forward_dependent_configs_from = invoker.forward_dependent_configs_from
-      }
-      if (defined(invoker.include_dirs)) {
-        include_dirs = invoker.include_dirs
-      }
-      if (defined(invoker.ldflags)) {
-        ldflags = invoker.ldflags
-      }
-      if (defined(invoker.lib_dirs)) {
-        lib_dirs = invoker.lib_dirs
-      }
-      if (defined(invoker.libs)) {
-        libs = invoker.libs
-      }
-      if (defined(invoker.output_extension)) {
-        output_extension = invoker.output_extension
-      }
-      if (defined(invoker.output_name)) {
-        output_name = invoker.output_name
-      }
-      if (defined(invoker.public)) {
-        public = invoker.public
-      }
-      if (defined(invoker.public_configs)) {
-        public_configs = invoker.public_configs
-      }
-      if (defined(invoker.public_deps)) {
-        public_deps = invoker.public_deps
-      }
-      if (defined(invoker.sources)) {
-        sources = invoker.sources
-      }
-      if (defined(invoker.visibility)) {
-        visibility = invoker.visibility
-      }
-    }
-  }
-}
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 37727692965b6..4778705998384 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -15,6 +15,7 @@ android_sdk_jar = "$android_sdk/android.jar"
 rebased_android_sdk_jar = rebase_path(android_sdk_jar, root_build_dir)
 
 template("android_lint") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -62,6 +63,7 @@ template("android_lint") {
 }
 
 template("dex") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -113,6 +115,7 @@ template("dex") {
 # Creates a zip archive of the inputs.
 # If base_dir is provided, the archive paths will be relative to it.
 template("zip") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -155,6 +158,7 @@ template("zip") {
 # See build/android/gyp/write_build_config.py and
 # build/android/gyp/util/build_utils.py:ExpandFileArgs
 template("write_build_config") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -288,6 +292,7 @@ template("write_build_config") {
 }
 
 template("process_java_prebuilt") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -375,6 +380,7 @@ template("process_java_prebuilt") {
 # Packages resources, assets, dex, and native libraries into an apk. Signs and
 # zipaligns the apk.
 template("create_apk") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -554,6 +560,7 @@ template("create_apk") {
 }
 
 template("java_prebuilt_impl") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -633,6 +640,7 @@ template("java_prebuilt_impl") {
 #   jar_path: Use this to explicitly set the output jar path. Defaults to
 #     "${target_gen_dir}/${target_name}.jar.
 template("compile_java") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -733,6 +741,7 @@ template("compile_java") {
 }
 
 template("java_library_impl") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -916,6 +925,7 @@ template("java_library_impl") {
 
 # Runs process_resources.py
 template("process_resources") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -1006,6 +1016,7 @@ template("process_resources") {
 }
 
 template("copy_ex") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -1053,6 +1064,7 @@ template("copy_ex") {
 
 # Produces a single .dex.jar out of a set of Java dependencies.
 template("deps_dex") {
+  set_sources_assignment_filter([])
   build_config = "$target_gen_dir/${target_name}.build_config"
   write_build_config("${target_name}__build_config") {
     type = "deps_dex"
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index b33cd88793fc9..07431d7a2c3b1 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -30,6 +30,7 @@ assert(is_android)
 #     jni_package = "foo"
 #   }
 template("generate_jni") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -128,6 +129,7 @@ template("generate_jni") {
 #     jni_package = "foo"
 #   }
 template("generate_jar_jni") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -237,6 +239,7 @@ template("generate_jar_jni") {
 #     include_path = "android/java/templates"
 #   }
 template("java_cpp_template") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -333,6 +336,7 @@ template("java_cpp_template") {
 #     ]
 #   }
 template("java_cpp_enum") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -392,6 +396,7 @@ template("java_cpp_enum") {
 #     output = "$target_gen_dir/AndroidManifest.xml"
 #   }
 template("jinja_template") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -448,6 +453,7 @@ template("jinja_template") {
 #     variables = ["color=red"]
 #   }
 template("jinja_template_resources") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -530,6 +536,7 @@ template("jinja_template_resources") {
 #     custom_package = "org.chromium.foo"
 #   }
 template("android_resources") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -600,6 +607,7 @@ template("android_resources") {
 #    grd_file = "foo_strings.grd"
 #  }
 template("java_strings_grd") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -666,6 +674,7 @@ template("java_strings_grd") {
 #    ]
 #  }
 template("java_strings_grd_prebuilt") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -725,6 +734,8 @@ template("java_strings_grd_prebuilt") {
 #     main_class = "org.chromium.foo.FooMain"
 #   }
 template("java_binary") {
+  set_sources_assignment_filter([])
+
   # TODO(cjhopman): This should not act like a java_library for dependents (i.e.
   # dependents shouldn't get the jar in their classpath, etc.).
   java_library_impl(target_name) {
@@ -810,6 +821,7 @@ template("java_binary") {
 #     ]
 #   }
 template("java_library") {
+  set_sources_assignment_filter([])
   java_library_impl(target_name) {
     if (defined(invoker.DEPRECATED_java_in_dir)) {
       DEPRECATED_java_in_dir = invoker.DEPRECATED_java_in_dir
@@ -876,6 +888,7 @@ template("java_library") {
 #     ]
 #   }
 template("java_prebuilt") {
+  set_sources_assignment_filter([])
   java_prebuilt_impl(target_name) {
     jar_path = invoker.jar_path
     if (defined(invoker.testonly)) {
@@ -942,6 +955,7 @@ template("java_prebuilt") {
 #     ]
 #   }
 template("android_library") {
+  set_sources_assignment_filter([])
   assert(!defined(invoker.jar_path),
          "android_library does not support a custom jar path")
   java_library_impl(target_name) {
@@ -1008,6 +1022,7 @@ template("android_library") {
 #     will be packaged into the resulting .dex.jar file.
 #   dex_path: location at which the output file will be put
 template("android_standalone_library") {
+  set_sources_assignment_filter([])
   deps_dex(target_name) {
     deps = invoker.deps
     dex_path = invoker.dex_path
@@ -1037,6 +1052,7 @@ template("android_standalone_library") {
 #     ]
 #   }
 template("android_java_prebuilt") {
+  set_sources_assignment_filter([])
   java_prebuilt_impl(target_name) {
     jar_path = invoker.jar_path
     supports_android = true
@@ -1105,6 +1121,7 @@ template("android_java_prebuilt") {
 #     ]
 #   }
 template("android_apk") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -1437,6 +1454,7 @@ template("android_apk") {
 #     unittests_dep = ":foo_unittests"
 #   }
 template("unittest_apk") {
+  set_sources_assignment_filter([])
   testonly = true
 
   assert(defined(invoker.unittests_dep), "Need unittests_dep for $target_name")
@@ -1508,6 +1526,7 @@ template("unittest_apk") {
 #     ]
 #   }
 template("android_aidl") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -1583,6 +1602,7 @@ template("android_aidl") {
 #     binary = "$root_build_dir/exe.stripped/foo"
 #   }
 template("create_native_executable_dist") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
@@ -1663,6 +1683,7 @@ template("create_native_executable_dist") {
 #    sources = [ "$proto_path/foo.proto" ]
 #  }
 template("proto_java_library") {
+  set_sources_assignment_filter([])
   _protoc_dep = "//third_party/android_protobuf:android_protoc($host_toolchain)"
   _protoc_out_dir = get_label_info(_protoc_dep, "root_out_dir")
   _protoc_bin = "$_protoc_out_dir/android_protoc"
@@ -1705,6 +1726,7 @@ template("proto_java_library") {
 
 # TODO(GYP): implement this.
 template("uiautomator_test") {
+  set_sources_assignment_filter([])
   if (defined(invoker.testonly)) {
     testonly = invoker.testonly
   }
diff --git a/build/secondary/third_party/cacheinvalidation/BUILD.gn b/build/secondary/third_party/cacheinvalidation/BUILD.gn
index 53d847296b562..088f89a4ee3e7 100644
--- a/build/secondary/third_party/cacheinvalidation/BUILD.gn
+++ b/build/secondary/third_party/cacheinvalidation/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 config("cacheinvalidation_config") {
   include_dirs = [
     "overrides",
diff --git a/build/secondary/third_party/leveldatabase/BUILD.gn b/build/secondary/third_party/leveldatabase/BUILD.gn
index 1ee030fbea65d..19eef1523f0fd 100644
--- a/build/secondary/third_party/leveldatabase/BUILD.gn
+++ b/build/secondary/third_party/leveldatabase/BUILD.gn
@@ -4,6 +4,9 @@
 
 # Snappy is a compression library we use.
 # TODO(brettw) It's not clear why this needs to be parameterized.
+
+import("//testing/test.gni")
+
 use_snappy = true
 
 defines = [ "LEVELDB_PLATFORM_CHROMIUM=1" ]
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 746cf7502e555..c3d77177b93ca 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 component("cc") {
   sources = [
     "animation/animation.cc",
diff --git a/cc/blink/BUILD.gn b/cc/blink/BUILD.gn
index f95291a62af50..e930277b2c483 100644
--- a/cc/blink/BUILD.gn
+++ b/cc/blink/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 # GYP version: //cc/blink/cc_blink.gyp:cc_blink
 component("blink") {
   output_name = "cc_blink"
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index ecc97345b7d3f..650560cc5a1a3 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -6,6 +6,7 @@ import("//build/config/android/config.gni")
 import("//build/config/android/rules.gni")
 import("//build/module_args/v8.gni")
 import("//chrome/version.gni")
+import("//testing/test.gni")
 import("//third_party/icu/config.gni")
 import("channel.gni")
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 965266f78038a..a7a677bac4d97 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5,6 +5,7 @@
 import("//build/config/features.gni")
 import("//build/config/ui.gni")
 import("//build/module_args/v8.gni")
+import("//testing/test.gni")
 
 # This target exists to reference other test executables to bring these files
 # into the build.
diff --git a/chrome/test/perf/BUILD.gn b/chrome/test/perf/BUILD.gn
index fd4b701d8e7ff..08a1fc92ab431 100644
--- a/chrome/test/perf/BUILD.gn
+++ b/chrome/test/perf/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 # This test appears to be a legacy target consisting of files not yet moved
 # elsewhere.
 test("perf") {
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index 7da2f4a087de3..0c9912e0893ba 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/config/allocator.gni")
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 import("//third_party/protobuf/proto_library.gni")
 
 assert(is_chromeos, "Non-ChromeOS builds must not depend on //chromeos")
diff --git a/components/BUILD.gn b/components/BUILD.gn
index cb58e34830ac5..921ac257feb6c 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/config/features.gni")
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 # Collection of all components. You wouldn't link to this, but this is rather
 # to reference the files so they can be compiled by the build system.
@@ -242,6 +243,7 @@ test("components_unittests") {
     "//components/domain_reliability:unit_tests",
     "//components/favicon_base:unit_tests",
     "//components/google/core/browser:unit_tests",
+    "//components/invalidation:unittests",
     "//components/login:unit_tests",
     "//components/metrics:unit_tests",
     "//components/omnibox:unit_tests",
diff --git a/components/proximity_auth/BUILD.gn b/components/proximity_auth/BUILD.gn
index 0ba93b02a43e2..d067603853864 100644
--- a/components/proximity_auth/BUILD.gn
+++ b/components/proximity_auth/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 source_set("proximity_auth") {
   sources = [
     "base64url.cc",
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 64cf6b4073e4d..45bd97ca7e94c 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -6,6 +6,7 @@ import("//build/config/crypto.gni")
 import("//build/config/features.gni")
 import("//build/config/ui.gni")
 import("//build/module_args/v8.gni")
+import("//testing/test.gni")
 import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
 
 content_tests_gypi_values =
@@ -363,6 +364,8 @@ if (!is_mac) {
         "//content/shell:content_shell_lib",
         "//testing/android:native_test_util",
       ]
+
+      use_launcher = false
     }
 
     if (is_mac) {
diff --git a/courgette/BUILD.gn b/courgette/BUILD.gn
index 9cf09431b3308..d277eb9e887da 100644
--- a/courgette/BUILD.gn
+++ b/courgette/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 static_library("courgette_lib") {
   sources = [
     "adjustment_method.cc",
diff --git a/crypto/BUILD.gn b/crypto/BUILD.gn
index 94388f7731106..f6c45aad72d3b 100644
--- a/crypto/BUILD.gn
+++ b/crypto/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/crypto.gni")
+import("//testing/test.gni")
 
 component("crypto") {
   output_name = "crcrypto"  # Avoid colliding with OpenSSL's libcrypto.
diff --git a/dbus/BUILD.gn b/dbus/BUILD.gn
index 895f3be8deaaf..ef6674e96351f 100644
--- a/dbus/BUILD.gn
+++ b/dbus/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("//third_party/protobuf/proto_library.gni")
 
 component("dbus") {
diff --git a/extensions/BUILD.gn b/extensions/BUILD.gn
index e3fdea56ca391..17ed557d48f79 100644
--- a/extensions/BUILD.gn
+++ b/extensions/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("//tools/grit/grit_rule.gni")
 import("//tools/grit/repack.gni")
 
diff --git a/gin/BUILD.gn b/gin/BUILD.gn
index d389b84d325aa..8f7e360b98ac3 100644
--- a/gin/BUILD.gn
+++ b/gin/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/module_args/v8.gni")
+import("//testing/test.gni")
 
 component("gin") {
   sources = [
diff --git a/google_apis/BUILD.gn b/google_apis/BUILD.gn
index bf71ce3bc0290..b07feb73c7b38 100644
--- a/google_apis/BUILD.gn
+++ b/google_apis/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/features.gni")
+import("//testing/test.gni")
 
 declare_args() {
   # You can set the variable 'use_official_google_api_keys' to true
diff --git a/google_apis/gcm/BUILD.gn b/google_apis/gcm/BUILD.gn
index d21a0573305e9..83412b690b880 100644
--- a/google_apis/gcm/BUILD.gn
+++ b/google_apis/gcm/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("//third_party/protobuf/proto_library.gni")
 
 component("gcm") {
diff --git a/gpu/BUILD.gn b/gpu/BUILD.gn
index fa9261f40acdc..e3846100f3e0d 100644
--- a/gpu/BUILD.gn
+++ b/gpu/BUILD.gn
@@ -33,6 +33,8 @@
 #
 # gpu/skia_bindings/skia_bindings.gyp:gpu_skia_bindings => //gpu/skia_bindings
 
+import("//testing/test.gni")
+
 component("gpu") {
   public_deps = [
     "//gpu/command_buffer/client",
diff --git a/ipc/BUILD.gn b/ipc/BUILD.gn
index e843a31f07a62..ac5631f15044b 100644
--- a/ipc/BUILD.gn
+++ b/ipc/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 component("ipc") {
   sources = [
     "ipc_channel.cc",
diff --git a/ipc/mojo/BUILD.gn b/ipc/mojo/BUILD.gn
index 7c04d181cef53..2c169a91c4197 100644
--- a/ipc/mojo/BUILD.gn
+++ b/ipc/mojo/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
 
 mojom("client_channel") {
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 1ed1cb04e351c..688c6a7299302 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -8,6 +8,7 @@ import("//build/config/features.gni")
 import("//build/config/linux/pkg_config.gni")
 import("//build/config/ui.gni")
 import("//media/media_options.gni")
+import("//testing/test.gni")
 
 # Common configuration for targets in the media directory.
 # NOT for exporting.
diff --git a/media/blink/BUILD.gn b/media/blink/BUILD.gn
index 46fecb0e4b31f..a328cc98adeec 100644
--- a/media/blink/BUILD.gn
+++ b/media/blink/BUILD.gn
@@ -1,6 +1,9 @@
 # Copyright 2014 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
+
+import("//testing/test.gni")
+
 component("blink") {
   output_name = "media_blink"
 
diff --git a/media/cast/BUILD.gn b/media/cast/BUILD.gn
index 629fcd9641946..632b334e56e5b 100644
--- a/media/cast/BUILD.gn
+++ b/media/cast/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 component("cast") {
   deps = [
     ":sender",
diff --git a/media/mojo/services/BUILD.gn b/media/mojo/services/BUILD.gn
index 63e37f2ebac52..88c64d1841cc6 100644
--- a/media/mojo/services/BUILD.gn
+++ b/media/mojo/services/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("//third_party/mojo/src/mojo/public/mojo_application.gni")
 
 # Target naming conventions:
diff --git a/mojo/common/BUILD.gn b/mojo/common/BUILD.gn
index 4248ff22b9314..ad01c318bb875 100644
--- a/mojo/common/BUILD.gn
+++ b/mojo/common/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 # GYP version: mojo/mojo_base.gyp:mojo_common_lib
 component("common") {
   output_name = "mojo_common_lib"
diff --git a/mojo/converters/surfaces/tests/BUILD.gn b/mojo/converters/surfaces/tests/BUILD.gn
index 6e60f084d75cb..efbd72fc5e656 100644
--- a/mojo/converters/surfaces/tests/BUILD.gn
+++ b/mojo/converters/surfaces/tests/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 # GYP version: mojo/mojo_converters.gypi:mojo_surfaces_lib_unittests
 test("mojo_surfaces_lib_unittests") {
   deps = [
diff --git a/mojo/services/html_viewer/BUILD.gn b/mojo/services/html_viewer/BUILD.gn
index d802032e18631..1d2c63dc90579 100644
--- a/mojo/services/html_viewer/BUILD.gn
+++ b/mojo/services/html_viewer/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("//third_party/mojo/src/mojo/public/mojo.gni")
 import("//third_party/mojo/src/mojo/public/mojo_application.gni")
 
diff --git a/mojo/services/view_manager/public/cpp/tests/BUILD.gn b/mojo/services/view_manager/public/cpp/tests/BUILD.gn
index ec164acd9ae27..2cbcf436e3659 100644
--- a/mojo/services/view_manager/public/cpp/tests/BUILD.gn
+++ b/mojo/services/view_manager/public/cpp/tests/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 test("mojo_view_manager_lib_unittests") {
   sources = [
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 90bf394857953..f235c3010b080 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -7,6 +7,7 @@ import("//build/config/features.gni")
 import("//build/config/ui.gni")
 import("//build/module_args/v8.gni")
 import("//url/config.gni")
+import("//testing/test.gni")
 
 # TODO(cjhopman): //build/config/android/rules.gni also imports grit_rule.gni.
 # Currently, that file can't be imported multiple times.  Make this always
diff --git a/printing/BUILD.gn b/printing/BUILD.gn
index d9cabe920d153..83a735d9b9364 100644
--- a/printing/BUILD.gn
+++ b/printing/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/config/features.gni")
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 if (is_mac) {
   import("//build/config/mac/mac_sdk.gni")
 }
diff --git a/rlz/BUILD.gn b/rlz/BUILD.gn
index 0887ef4726a3d..f8891370dc03b 100644
--- a/rlz/BUILD.gn
+++ b/rlz/BUILD.gn
@@ -5,6 +5,8 @@
 # Note that this build file assumes rlz_use_chrome_net which is a condition in
 # the GYP file, but is always true for Chrome builds.
 
+import("//testing/test.gni")
+
 config("rlz_config") {
   defines = [ "RLZ_NETWORK_IMPLEMENTATION_CHROME_NET" ]
 }
diff --git a/sandbox/linux/BUILD.gn b/sandbox/linux/BUILD.gn
index 21e19fd1bad0f..b5cfcdb948086 100644
--- a/sandbox/linux/BUILD.gn
+++ b/sandbox/linux/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/features.gni")
+import("//testing/test.gni")
 
 declare_args() {
   compile_suid_client = is_linux
diff --git a/sandbox/mac/BUILD.gn b/sandbox/mac/BUILD.gn
index 64f9c9cf1d392..e2c39ee0e280f 100644
--- a/sandbox/mac/BUILD.gn
+++ b/sandbox/mac/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/mac/mac_sdk.gni")
+import("//testing/test.gni")
 
 component("sandbox") {
   sources = [
diff --git a/sandbox/win/BUILD.gn b/sandbox/win/BUILD.gn
index d5959035c823a..fe37f2268ac0f 100644
--- a/sandbox/win/BUILD.gn
+++ b/sandbox/win/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 source_set("sandbox") {
   sources = [
     "src/acl.cc",
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index eb54593088922..8bb5dcda5bc36 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -4,12 +4,10 @@
 
 import("//build/config/features.gni")
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 if (cpu_arch == "arm") {
   import("//build/config/arm.gni")
 }
-if (is_android) {
-  import("//build/config/android/rules.gni")
-}
 
 skia_support_gpu = !is_ios
 skia_support_pdf = !is_ios && (enable_basic_printing || enable_print_preview)
@@ -746,13 +744,3 @@ test("skia_unittests") {
     "//ui/gfx/geometry",
   ]
 }
-
-if (is_android) {
-  # GYP: //skia/skia_tests.gyp:skia_unittests_apk
-  unittest_apk("skia_unittests_apk") {
-    unittests_dep = ":skia_unittests"
-    deps = [
-      ":skia_unittests",
-    ]
-  }
-}
diff --git a/sql/BUILD.gn b/sql/BUILD.gn
index 21a6f85d94fb2..05185d5cc26eb 100644
--- a/sql/BUILD.gn
+++ b/sql/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 component("sql") {
   sources = [
     "connection.cc",
diff --git a/sync/BUILD.gn b/sync/BUILD.gn
index 65af3de406064..df5f23a928267 100644
--- a/sync/BUILD.gn
+++ b/sync/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/features.gni")
+import("//testing/test.gni")
 
 component("sync") {
   public_deps = [
@@ -645,13 +646,6 @@ test("sync_unit_tests") {
     ":test_support_sync_internal_api",
   ]
 
-  # TODO(GYP)
-  #   ['OS == "android"', {
-  #     'dependencies': [
-  #       '../testing/android/native_test.gyp:native_test_native_code',
-  #     ],
-  #   }],
-
   if (is_chromeos) {
     # Required by get_session_name_unittest.cc on Chrome OS.
     deps += [ "//chromeos" ]
@@ -812,12 +806,4 @@ if (is_android) {
       "//base",
     ]
   }
-
-  # GYP: //sync/sync_tests.gypi:sync_unit_tests_apk
-  unittest_apk("sync_unit_tests_apk") {
-    unittests_dep = ":sync_unit_tests"
-    deps = [
-      ":sync_unit_tests",
-    ]
-  }
 }
diff --git a/testing/test.gni b/testing/test.gni
index 50b23dff631db..9362a78f303cc 100644
--- a/testing/test.gni
+++ b/testing/test.gni
@@ -1,3 +1,237 @@
 # Copyright 2015 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
+
+# ==============================================================================
+# TEST SETUP
+# ==============================================================================
+
+# Define a test as an executable (or apk on Android) with the "testonly" flag
+# set.
+template("test") {
+  if (is_android) {
+    import("//build/config/android/config.gni")
+    import("//build/config/android/rules.gni")
+
+    main_target_name = target_name
+    library_name = "_${target_name}__library"
+    apk_name = "${target_name}_apk"
+
+    shared_library(library_name) {
+      # Configs will always be defined since we set_defaults for a component
+      # in the main config. We want to use those rather than whatever came with
+      # the nested shared/static library inside the component.
+      configs = []  # Prevent list overwriting warning.
+      configs = invoker.configs
+
+      # See above call.
+      set_sources_assignment_filter([])
+
+      testonly = true
+
+      if (defined(invoker.all_dependent_configs)) {
+        all_dependent_configs = invoker.all_dependent_configs
+      }
+      if (defined(invoker.allow_circular_includes_from)) {
+        allow_circular_includes_from = invoker.allow_circular_includes_from
+      }
+      if (defined(invoker.cflags)) {
+        cflags = invoker.cflags
+      }
+      if (defined(invoker.cflags_c)) {
+        cflags_c = invoker.cflags_c
+      }
+      if (defined(invoker.cflags_cc)) {
+        cflags_cc = invoker.cflags_cc
+      }
+      if (defined(invoker.cflags_objc)) {
+        cflags_objc = invoker.cflags_objc
+      }
+      if (defined(invoker.cflags_objcc)) {
+        cflags_objcc = invoker.cflags_objcc
+      }
+      if (defined(invoker.check_includes)) {
+        check_includes = invoker.check_includes
+      }
+      if (defined(invoker.data)) {
+        data = invoker.data
+      }
+      if (defined(invoker.data_deps)) {
+        data_deps = invoker.data_deps
+      }
+      if (defined(invoker.datadeps)) {
+        datadeps = invoker.datadeps
+      }
+      if (defined(invoker.defines)) {
+        defines = invoker.defines
+      }
+      deps = []
+      if (!defined(invoker.use_launcher) || invoker.use_launcher) {
+        deps += [ "//testing/android:native_test_native_code" ]
+      }
+      if (defined(invoker.deps)) {
+        deps += invoker.deps
+      }
+      if (defined(invoker.direct_dependent_configs)) {
+        direct_dependent_configs = invoker.direct_dependent_configs
+      }
+      if (defined(invoker.forward_dependent_configs_from)) {
+        forward_dependent_configs_from = invoker.forward_dependent_configs_from
+      }
+      if (defined(invoker.include_dirs)) {
+        include_dirs = invoker.include_dirs
+      }
+      if (defined(invoker.ldflags)) {
+        ldflags = invoker.ldflags
+      }
+      if (defined(invoker.lib_dirs)) {
+        lib_dirs = invoker.lib_dirs
+      }
+      if (defined(invoker.libs)) {
+        libs = invoker.libs
+      }
+      if (defined(invoker.output_extension)) {
+        output_extension = invoker.output_extension
+      }
+      if (defined(invoker.output_name)) {
+        output_name = invoker.output_name
+      }
+      if (defined(invoker.public)) {
+        public = invoker.public
+      }
+      if (defined(invoker.public_configs)) {
+        public_configs = invoker.public_configs
+      }
+      if (defined(invoker.public_deps)) {
+        public_deps = invoker.public_deps
+      }
+      if (defined(invoker.sources)) {
+        sources = invoker.sources
+      }
+      if (defined(invoker.visibility)) {
+        visibility = invoker.visibility
+      }
+    }
+
+    unittest_apk(apk_name) {
+      unittests_dep = ":$library_name"
+      apk_name = main_target_name
+      if (defined(invoker.output_name)) {
+        test_output_name = invoker.output_name
+        unittests_binary = "lib${test_output_name}.so"
+      }
+      deps = [
+        ":$library_name",
+      ]
+      if (defined(invoker.apk_deps)) {
+        deps += invoker.apk_deps
+      }
+    }
+
+    group(target_name) {
+      testonly = true
+
+      deps = [
+        ":$library_name",
+        ":$apk_name",
+      ]
+    }
+  } else {
+    executable(target_name) {
+      # See above.
+      configs = []  # Prevent list overwriting warning.
+      configs = invoker.configs
+
+      # See above call.
+      set_sources_assignment_filter([])
+
+      testonly = true
+
+      if (defined(invoker.all_dependent_configs)) {
+        all_dependent_configs = invoker.all_dependent_configs
+      }
+      if (defined(invoker.allow_circular_includes_from)) {
+        allow_circular_includes_from = invoker.allow_circular_includes_from
+      }
+      if (defined(invoker.cflags)) {
+        cflags = invoker.cflags
+      }
+      if (defined(invoker.cflags_c)) {
+        cflags_c = invoker.cflags_c
+      }
+      if (defined(invoker.cflags_cc)) {
+        cflags_cc = invoker.cflags_cc
+      }
+      if (defined(invoker.cflags_objc)) {
+        cflags_objc = invoker.cflags_objc
+      }
+      if (defined(invoker.cflags_objcc)) {
+        cflags_objcc = invoker.cflags_objcc
+      }
+      if (defined(invoker.check_includes)) {
+        check_includes = invoker.check_includes
+      }
+      if (defined(invoker.data)) {
+        data = invoker.data
+      }
+      if (defined(invoker.data_deps)) {
+        data_deps = invoker.data_deps
+      }
+      if (defined(invoker.datadeps)) {
+        datadeps = invoker.datadeps
+      }
+      if (defined(invoker.defines)) {
+        defines = invoker.defines
+      }
+
+      # All shared libraries must have the sanitizer deps to properly link in
+      # asan mode (this target will be empty in other cases).
+      if (defined(invoker.deps)) {
+        deps = invoker.deps + [ "//build/config/sanitizers:deps" ]
+      } else {
+        deps = [
+          "//build/config/sanitizers:deps",
+        ]
+      }
+      if (defined(invoker.direct_dependent_configs)) {
+        direct_dependent_configs = invoker.direct_dependent_configs
+      }
+      if (defined(invoker.forward_dependent_configs_from)) {
+        forward_dependent_configs_from = invoker.forward_dependent_configs_from
+      }
+      if (defined(invoker.include_dirs)) {
+        include_dirs = invoker.include_dirs
+      }
+      if (defined(invoker.ldflags)) {
+        ldflags = invoker.ldflags
+      }
+      if (defined(invoker.lib_dirs)) {
+        lib_dirs = invoker.lib_dirs
+      }
+      if (defined(invoker.libs)) {
+        libs = invoker.libs
+      }
+      if (defined(invoker.output_extension)) {
+        output_extension = invoker.output_extension
+      }
+      if (defined(invoker.output_name)) {
+        output_name = invoker.output_name
+      }
+      if (defined(invoker.public)) {
+        public = invoker.public
+      }
+      if (defined(invoker.public_configs)) {
+        public_configs = invoker.public_configs
+      }
+      if (defined(invoker.public_deps)) {
+        public_deps = invoker.public_deps
+      }
+      if (defined(invoker.sources)) {
+        sources = invoker.sources
+      }
+      if (defined(invoker.visibility)) {
+        visibility = invoker.visibility
+      }
+    }
+  }
+}
diff --git a/third_party/libaddressinput/BUILD.gn b/third_party/libaddressinput/BUILD.gn
index 99c2be2cdd21d..5f6d4fc04464c 100644
--- a/third_party/libaddressinput/BUILD.gn
+++ b/third_party/libaddressinput/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("//tools/grit/grit_rule.gni")
 
 libaddressinput_util_files = [
diff --git a/third_party/libphonenumber/BUILD.gn b/third_party/libphonenumber/BUILD.gn
index 22d13475be5fc..4c32084ef42b6 100644
--- a/third_party/libphonenumber/BUILD.gn
+++ b/third_party/libphonenumber/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("//third_party/protobuf/proto_library.gni")
 
 proto_library("proto") {
diff --git a/third_party/mojo/src/mojo/edk/js/test/BUILD.gn b/third_party/mojo/src/mojo/edk/js/test/BUILD.gn
index badc3ad5d24c6..326de6aa7e7a4 100644
--- a/third_party/mojo/src/mojo/edk/js/test/BUILD.gn
+++ b/third_party/mojo/src/mojo/edk/js/test/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 test("js_unittests") {
   deps = [
     "../../js",
diff --git a/third_party/mojo/src/mojo/edk/system/BUILD.gn b/third_party/mojo/src/mojo/edk/system/BUILD.gn
index 2f8abdde61511..eab85a231d645 100644
--- a/third_party/mojo/src/mojo/edk/system/BUILD.gn
+++ b/third_party/mojo/src/mojo/edk/system/BUILD.gn
@@ -2,13 +2,9 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("../mojo_edk.gni")
 
-if (is_android) {
-  import("//build/config/android/config.gni")
-  import("//build/config/android/rules.gni")
-}
-
 config("system_config") {
   defines = [
     # Ensures that dependent projects import the core functions on Windows.
@@ -173,10 +169,6 @@ test("mojo_system_unittests") {
     "//testing/gtest",
   ]
 
-  if (is_android) {
-    deps += [ "//testing/android:native_test_native_code" ]
-  }
-
   allow_circular_includes_from = [ "../embedder:embedder_unittests" ]
 }
 
@@ -198,12 +190,3 @@ test("mojo_message_pipe_perftests") {
     "//testing/gtest",
   ]
 }
-
-if (is_android) {
-  unittest_apk("mojo_system_unittests_apk") {
-    deps = [
-      ":mojo_system_unittests",
-    ]
-    unittests_dep = ":mojo_system_unittests"
-  }
-}
diff --git a/third_party/mojo/src/mojo/edk/test/BUILD.gn b/third_party/mojo/src/mojo/edk/test/BUILD.gn
index d05f6d44c9735..ea323c60921b9 100644
--- a/third_party/mojo/src/mojo/edk/test/BUILD.gn
+++ b/third_party/mojo/src/mojo/edk/test/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("../mojo_edk.gni")
 
 mojo_edk_source_set("test_support") {
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index 40c28a2ae6bd3..a61e456e3645e 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 defines = [ "GN_BUILD" ]
 
 static_library("gn_lib") {
diff --git a/tools/gn/format_test_data/030.gn b/tools/gn/format_test_data/030.gn
index 4ec57a6a69ecf..adac9a8241644 100644
--- a/tools/gn/format_test_data/030.gn
+++ b/tools/gn/format_test_data/030.gn
@@ -1,4 +1,7 @@
 # Don't separate simple statements in a scope.
+
+import("//testing/test.gni")
+
 test("something") {
   if (is_linux) {
     sources -= [ "file_version_info_unittest.cc" ]
diff --git a/tools/relocation_packer/BUILD.gn b/tools/relocation_packer/BUILD.gn
index cbbc6fdde5910..0b29c9162eb4f 100644
--- a/tools/relocation_packer/BUILD.gn
+++ b/tools/relocation_packer/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("config.gni")
+import("//testing/test.gni")
 
 assert(relocation_packing_supported)
 
diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn
index ac5f2aaf0c52b..2f8868fcae0ee 100644
--- a/ui/accessibility/BUILD.gn
+++ b/ui/accessibility/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/json_schema_api.gni")
+import("//testing/test.gni")
 
 component("accessibility") {
   sources = [
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn
index e496cb9cb0950..b725452fe4b55 100644
--- a/ui/android/BUILD.gn
+++ b/ui/android/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/android/rules.gni")
+import("//testing/test.gni")
 
 assert(is_android)
 
@@ -153,18 +154,10 @@ test("ui_android_unittests") {
     "//base/test:test_support",
     "//cc",
     "//skia",
-    "//testing/android:native_test_native_code",
     "//testing/gtest",
     "//ui/base",
     "//ui/gfx",
     "//ui/resources:ui_test_pak",
   ]
-}
-
-unittest_apk("ui_android_unittests_apk") {
-  deps = [
-    ":ui_android_unittests",
-    ":ui_java",
-  ]
-  unittests_dep = ":ui_android_unittests"
+  apk_deps = [ ":ui_java" ]
 }
diff --git a/ui/app_list/BUILD.gn b/ui/app_list/BUILD.gn
index e6e0e424d8fff..8746c3f73cdde 100644
--- a/ui/app_list/BUILD.gn
+++ b/ui/app_list/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 component("app_list") {
   sources = [
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index 1357a63c1ec50..0ebe448e17594 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 component("aura") {
   sources = [
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index 3e592312dd8da..a2633c941b61d 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 if (is_android) {
   import("//build/config/android/config.gni")
@@ -878,9 +879,7 @@ test("ui_base_unittests") {
   }
 
   if (is_android) {
-    deps += [
-      #"testing/android/native_test.gyp:native_test_native_code"  TODO(GYP)
-    ]
+    apk_deps = [ "//chrome:resources" ]
   }
 
   if (use_pango) {
@@ -929,14 +928,4 @@ test("ui_base_unittests") {
     deps += [ "//chromeos" ]
   }
 }
-
 # TODO(GYP) Mac (ui_base_tests_bundle)
-if (is_android) {
-  unittest_apk("ui_base_unittests_apk") {
-    unittests_dep = ":ui_base_unittests"
-    deps = [
-      ":ui_base_unittests",
-      "//chrome:resources",
-    ]
-  }
-}
diff --git a/ui/chromeos/BUILD.gn b/ui/chromeos/BUILD.gn
index 6b34562be68a0..bcfaad4c6a9ad 100644
--- a/ui/chromeos/BUILD.gn
+++ b/ui/chromeos/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
+
 component("ui_chromeos") {
   sources = [
     "accessibility_types.h",
diff --git a/ui/compositor/BUILD.gn b/ui/compositor/BUILD.gn
index 047aa166adc40..7bc09aca9c23a 100644
--- a/ui/compositor/BUILD.gn
+++ b/ui/compositor/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 component("compositor") {
   sources = [
diff --git a/ui/display/BUILD.gn b/ui/display/BUILD.gn
index 998be45a073ae..a32e49926f5ce 100644
--- a/ui/display/BUILD.gn
+++ b/ui/display/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 component("display") {
   sources = [
diff --git a/ui/events/BUILD.gn b/ui/events/BUILD.gn
index 805392e9a1a67..c0c948ffcd7df 100644
--- a/ui/events/BUILD.gn
+++ b/ui/events/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 static_library("dom4_keycode_converter") {
   sources = [
diff --git a/ui/gfx/BUILD.gn b/ui/gfx/BUILD.gn
index d69cd349532e8..09cab868fe5e1 100644
--- a/ui/gfx/BUILD.gn
+++ b/ui/gfx/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 if (is_android) {
   import("//build/config/android/config.gni")
diff --git a/ui/keyboard/BUILD.gn b/ui/keyboard/BUILD.gn
index b40197231c327..15b8cdfbed4da 100644
--- a/ui/keyboard/BUILD.gn
+++ b/ui/keyboard/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
 import("//third_party/google_input_tools/closure.gni")
 import("//third_party/google_input_tools/inputview.gni")
diff --git a/ui/message_center/BUILD.gn b/ui/message_center/BUILD.gn
index 613a5bfe71ea9..2a4e64318d4ce 100644
--- a/ui/message_center/BUILD.gn
+++ b/ui/message_center/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/config/features.gni")
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 component("message_center") {
   deps = [
diff --git a/ui/ozone/BUILD.gn b/ui/ozone/BUILD.gn
index 40d45cf732874..543f81cd6edac 100644
--- a/ui/ozone/BUILD.gn
+++ b/ui/ozone/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//ui/ozone/ozone.gni")
+import("//testing/test.gni")
 
 # The list of platforms that will be built.
 ozone_platforms = []
diff --git a/ui/shell_dialogs/BUILD.gn b/ui/shell_dialogs/BUILD.gn
index 5bd62c073c2c9..f4f89f7c92809 100644
--- a/ui/shell_dialogs/BUILD.gn
+++ b/ui/shell_dialogs/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 if (is_android) {
   import("//build/config/android/config.gni")
 }
diff --git a/ui/snapshot/BUILD.gn b/ui/snapshot/BUILD.gn
index 972218afda9ec..49a6bd8cb03e2 100644
--- a/ui/snapshot/BUILD.gn
+++ b/ui/snapshot/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 component("snapshot") {
   sources = [
diff --git a/ui/touch_selection/BUILD.gn b/ui/touch_selection/BUILD.gn
index 3b477e481083a..e6767cb4f9145 100644
--- a/ui/touch_selection/BUILD.gn
+++ b/ui/touch_selection/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 if (is_android) {
   import("//build/config/android/rules.gni")
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 5b24f7f3ff879..c5257b4916e1f 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/config/features.gni")
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 gypi_values = exec_script("//build/gypi_to_gn.py",
                           [ rebase_path("views.gyp") ],
diff --git a/ui/wm/BUILD.gn b/ui/wm/BUILD.gn
index d5e3f2932221c..116ef83af6152 100644
--- a/ui/wm/BUILD.gn
+++ b/ui/wm/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/ui.gni")
+import("//testing/test.gni")
 
 component("wm") {
   sources = [
diff --git a/url/BUILD.gn b/url/BUILD.gn
index 3bffec26cf175..03e90cb3fff08 100644
--- a/url/BUILD.gn
+++ b/url/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/test.gni")
 import("//url/config.gni")
 
 # Sets the USE_ICU_ALTERNATIVES_ON_ANDROID define based on the build flag.