diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a80717e6..679254fd 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,7 +1,7 @@
 repos:
 
 - repo: https://github.com/pre-commit/pre-commit-hooks
-  rev: v4.2.0
+  rev: v4.3.0
   hooks:
   - id: check-case-conflict
   - id: check-merge-conflict
@@ -14,7 +14,7 @@ repos:
   - id: trailing-whitespace
 
 - repo: https://github.com/asottile/pyupgrade
-  rev: v2.32.1
+  rev: v2.34.0
   hooks:
   - id: pyupgrade
     name: PyUpgrade 3.6+
@@ -49,7 +49,7 @@ repos:
   - id: setup-cfg-fmt
 
 - repo: https://github.com/pre-commit/mirrors-mypy
-  rev: v0.960
+  rev: v0.961
   hooks:
   - id: mypy
     name: mypy 3.6 on cibuildwheel/
diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py
index 064f3fca..db0cc687 100644
--- a/cibuildwheel/util.py
+++ b/cibuildwheel/util.py
@@ -234,21 +234,20 @@ def selector_matches(patterns: str, string: str) -> bool:
 
 # Once we require Python 3.10+, we can add kw_only=True
 @dataclasses.dataclass
-class IdentifierSelector:
+class BuildSelector:
     """
     This class holds a set of build/skip patterns. You call an instance with a
     build identifier, and it returns True if that identifier should be
     included. Only call this on valid identifiers, ones that have at least 2
-    numeric digits before the first dash. If a pre-release version X.Y is present,
-    you can filter it with prerelease="XY".
+    numeric digits before the first dash.
     """
 
-    # a pattern that skips prerelease versions, when include_prereleases is False.
-    PRERELEASE_SKIP: ClassVar[str] = "cp311-*"
-
-    skip_config: str
     build_config: str
+    skip_config: str
     requires_python: Optional[SpecifierSet] = None
+
+    # a pattern that skips prerelease versions, when include_prereleases is False.
+    PRERELEASE_SKIP: ClassVar[str] = "cp311-*"
     prerelease_pythons: bool = False
 
     def __call__(self, build_id: str) -> bool:
@@ -272,15 +271,16 @@ def __call__(self, build_id: str) -> bool:
 
 
 @dataclasses.dataclass
-class BuildSelector(IdentifierSelector):
-    pass
+class TestSelector:
+    """
+    A build selector that can only skip tests according to a skip pattern.
+    """
 
+    skip_config: str
 
-# Note that requires-python is not needed for TestSelector, as you can't test
-# what you can't build.
-@dataclasses.dataclass
-class TestSelector(IdentifierSelector):
-    build_config: str = "*"
+    def __call__(self, build_id: str) -> bool:
+        should_skip = selector_matches(self.skip_config, build_id)
+        return not should_skip
 
 
 # Taken from https://stackoverflow.com/a/107717

diff --git a/unit_test/build_selector_test.py b/unit_test/build_selector_test.py
index 6b31d1c0..b71cb05f 100644
--- a/unit_test/build_selector_test.py
+++ b/unit_test/build_selector_test.py
@@ -136,3 +136,14 @@ def test_build_limited_python_patch():
 
     assert build_selector("cp36-manylinux_x86_64")
     assert build_selector("cp37-manylinux_x86_64")
+
+
+def test_testing_selector():
+    # local import to avoid pytest trying to collect this as a test class!
+    from cibuildwheel.util import TestSelector
+
+    test_selector = TestSelector(skip_config="cp36-*")
+
+    assert not test_selector("cp36-win_amd64")
+    assert test_selector("cp37-manylinux_x86_64")
+    assert test_selector("cp311-manylinux_x86_64")

Tests are skipped on pre-release Pythons
### Description

On cibuildwheel==2.6.1, when running with `CIBW_PRERELEASE_PYTHONS=1`, any configured test command is always skipped when targeting a pre-release Python version. A quick glance at the code makes me think it's an unintended consequence of having `TestSelector` inherit the prerelease exclusion behavior from `IdentifierSelector`.


### Build log

https://github.com/rolpdog/cffi-mirror/runs/6867587654?check_suite_focus=true

### CI config

https://github.com/rolpdog/cffi-mirror/commit/b7a01156bafba52bd2bfedc1222c9eb8d36491c7#diff-944291df2c9c06359d37cc8833d182d705c9e8c3108e7cfe132d61a06e9133dd
unit_test/build_selector_test.py::test_testing_selector
unit_test/build_selector_test.py::test_build unit_test/build_selector_test.py::test_build_filter_pre unit_test/build_selector_test.py::test_skip unit_test/build_selector_test.py::test_build_and_skip unit_test/build_selector_test.py::test_build_braces unit_test/build_selector_test.py::test_build_limited_python unit_test/build_selector_test.py::test_build_limited_python_partial unit_test/build_selector_test.py::test_build_limited_python_patch