diff --git a/Lib/test/test_capi/test_frame.py b/Lib/test/test_capi/test_frame.py
new file mode 100644
index 00000000000000..23cb8e3dada9d4
--- /dev/null
+++ b/Lib/test/test_capi/test_frame.py
@@ -0,0 +1,56 @@
+import sys
+import unittest
+from test.support import import_helper
+
+
+_testcapi = import_helper.import_module('_testcapi')
+
+
+class FrameTest(unittest.TestCase):
+    def getframe(self):
+        return sys._getframe()
+
+    def test_frame_getters(self):
+        frame = self.getframe()
+        self.assertEqual(frame.f_locals, _testcapi.frame_getlocals(frame))
+        self.assertIs(frame.f_globals, _testcapi.frame_getglobals(frame))
+        self.assertIs(frame.f_builtins, _testcapi.frame_getbuiltins(frame))
+        self.assertEqual(frame.f_lasti, _testcapi.frame_getlasti(frame))
+
+    def test_getvar(self):
+        current_frame = sys._getframe()
+        x = 1
+        self.assertEqual(_testcapi.frame_getvar(current_frame, "x"), 1)
+        self.assertEqual(_testcapi.frame_getvarstring(current_frame, b"x"), 1)
+        with self.assertRaises(NameError):
+            _testcapi.frame_getvar(current_frame, "y")
+        with self.assertRaises(NameError):
+            _testcapi.frame_getvarstring(current_frame, b"y")
+
+        # wrong name type
+        with self.assertRaises(TypeError):
+            _testcapi.frame_getvar(current_frame, b'x')
+        with self.assertRaises(TypeError):
+            _testcapi.frame_getvar(current_frame, 123)
+
+    def getgenframe(self):
+        yield sys._getframe()
+
+    def test_frame_get_generator(self):
+        gen = self.getgenframe()
+        frame = next(gen)
+        self.assertIs(gen, _testcapi.frame_getgenerator(frame))
+
+    def test_frame_fback_api(self):
+        """Test that accessing `f_back` does not cause a segmentation fault on
+        a frame created with `PyFrame_New` (GH-99110)."""
+        def dummy():
+            pass
+
+        frame = _testcapi.frame_new(dummy.__code__, globals(), locals())
+        # The following line should not cause a segmentation fault.
+        self.assertIsNone(frame.f_back)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py
index 7bd13eada8fedf..4d086064023488 100644
--- a/Lib/test/test_frame.py
+++ b/Lib/test/test_frame.py
@@ -773,51 +773,6 @@ def f():
             self.assertIs(catcher.unraisable.exc_type, TypeError)
         self.assertIsNone(weak())
 
-@unittest.skipIf(_testcapi is None, 'need _testcapi')
-class TestCAPI(unittest.TestCase):
-    def getframe(self):
-        return sys._getframe()
-
-    def test_frame_getters(self):
-        frame = self.getframe()
-        self.assertEqual(frame.f_locals, _testcapi.frame_getlocals(frame))
-        self.assertIs(frame.f_globals, _testcapi.frame_getglobals(frame))
-        self.assertIs(frame.f_builtins, _testcapi.frame_getbuiltins(frame))
-        self.assertEqual(frame.f_lasti, _testcapi.frame_getlasti(frame))
-
-    def test_getvar(self):
-        current_frame = sys._getframe()
-        x = 1
-        self.assertEqual(_testcapi.frame_getvar(current_frame, "x"), 1)
-        self.assertEqual(_testcapi.frame_getvarstring(current_frame, b"x"), 1)
-        with self.assertRaises(NameError):
-            _testcapi.frame_getvar(current_frame, "y")
-        with self.assertRaises(NameError):
-            _testcapi.frame_getvarstring(current_frame, b"y")
-
-        # wrong name type
-        with self.assertRaises(TypeError):
-            _testcapi.frame_getvar(current_frame, b'x')
-        with self.assertRaises(TypeError):
-            _testcapi.frame_getvar(current_frame, 123)
-
-    def getgenframe(self):
-        yield sys._getframe()
-
-    def test_frame_get_generator(self):
-        gen = self.getgenframe()
-        frame = next(gen)
-        self.assertIs(gen, _testcapi.frame_getgenerator(frame))
-
-    def test_frame_fback_api(self):
-        """Test that accessing `f_back` does not cause a segmentation fault on
-        a frame created with `PyFrame_New` (GH-99110)."""
-        def dummy():
-            pass
-
-        frame = _testcapi.frame_new(dummy.__code__, globals(), locals())
-        # The following line should not cause a segmentation fault.
-        self.assertIsNone(frame.f_back)
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index b31bf2734e4fd1..1fd7c8bd2e2d73 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -162,7 +162,7 @@
 @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
 @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
 @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c
 @MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
 @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_testcapi/frame.c b/Modules/_testcapi/frame.c
new file mode 100644
index 00000000000000..5748dca948ea94
--- /dev/null
+++ b/Modules/_testcapi/frame.c
@@ -0,0 +1,134 @@
+#include "parts.h"
+#include "util.h"
+
+#include "frameobject.h"          // PyFrame_New()
+
+
+static PyObject *
+frame_getlocals(PyObject *self, PyObject *frame)
+{
+    if (!PyFrame_Check(frame)) {
+        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
+        return NULL;
+    }
+    return PyFrame_GetLocals((PyFrameObject *)frame);
+}
+
+
+static PyObject *
+frame_getglobals(PyObject *self, PyObject *frame)
+{
+    if (!PyFrame_Check(frame)) {
+        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
+        return NULL;
+    }
+    return PyFrame_GetGlobals((PyFrameObject *)frame);
+}
+
+
+static PyObject *
+frame_getgenerator(PyObject *self, PyObject *frame)
+{
+    if (!PyFrame_Check(frame)) {
+        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
+        return NULL;
+    }
+    return PyFrame_GetGenerator((PyFrameObject *)frame);
+}
+
+
+static PyObject *
+frame_getbuiltins(PyObject *self, PyObject *frame)
+{
+    if (!PyFrame_Check(frame)) {
+        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
+        return NULL;
+    }
+    return PyFrame_GetBuiltins((PyFrameObject *)frame);
+}
+
+
+static PyObject *
+frame_getlasti(PyObject *self, PyObject *frame)
+{
+    if (!PyFrame_Check(frame)) {
+        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
+        return NULL;
+    }
+    int lasti = PyFrame_GetLasti((PyFrameObject *)frame);
+    if (lasti < 0) {
+        assert(lasti == -1);
+        Py_RETURN_NONE;
+    }
+    return PyLong_FromLong(lasti);
+}
+
+
+static PyObject *
+frame_new(PyObject *self, PyObject *args)
+{
+    PyObject *code, *globals, *locals;
+    if (!PyArg_ParseTuple(args, "OOO", &code, &globals, &locals)) {
+        return NULL;
+    }
+    if (!PyCode_Check(code)) {
+        PyErr_SetString(PyExc_TypeError, "argument must be a code object");
+        return NULL;
+    }
+    PyThreadState *tstate = PyThreadState_Get();
+
+    return (PyObject *)PyFrame_New(tstate, (PyCodeObject *)code, globals, locals);
+}
+
+
+static PyObject *
+frame_getvar(PyObject *self, PyObject *args)
+{
+    PyObject *frame, *name;
+    if (!PyArg_ParseTuple(args, "OO", &frame, &name)) {
+        return NULL;
+    }
+    if (!PyFrame_Check(frame)) {
+        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
+        return NULL;
+    }
+
+    return PyFrame_GetVar((PyFrameObject *)frame, name);
+}
+
+
+static PyObject *
+frame_getvarstring(PyObject *self, PyObject *args)
+{
+    PyObject *frame;
+    const char *name;
+    if (!PyArg_ParseTuple(args, "Oy", &frame, &name)) {
+        return NULL;
+    }
+    if (!PyFrame_Check(frame)) {
+        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
+        return NULL;
+    }
+
+    return PyFrame_GetVarString((PyFrameObject *)frame, name);
+}
+
+
+static PyMethodDef test_methods[] = {
+    {"frame_getlocals", frame_getlocals, METH_O, NULL},
+    {"frame_getglobals", frame_getglobals, METH_O, NULL},
+    {"frame_getgenerator", frame_getgenerator, METH_O, NULL},
+    {"frame_getbuiltins", frame_getbuiltins, METH_O, NULL},
+    {"frame_getlasti", frame_getlasti, METH_O, NULL},
+    {"frame_new", frame_new, METH_VARARGS, NULL},
+    {"frame_getvar", frame_getvar, METH_VARARGS, NULL},
+    {"frame_getvarstring", frame_getvarstring, METH_VARARGS, NULL},
+    {NULL},
+};
+
+int
+_PyTestCapi_Init_Frame(PyObject *m)
+{
+    return PyModule_AddFunctions(m, test_methods);
+}
+
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index 792552d8097312..e5d2502edf5362 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -62,5 +62,6 @@ int _PyTestCapi_Init_Monitoring(PyObject *module);
 int _PyTestCapi_Init_Object(PyObject *module);
 int _PyTestCapi_Init_Config(PyObject *mod);
 int _PyTestCapi_Init_Import(PyObject *mod);
+int _PyTestCapi_Init_Frame(PyObject *mod);
 
 #endif // Py_TESTCAPI_PARTS_H
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 4da23ba82d5756..d25e61dbc3d588 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2533,109 +2533,6 @@ test_tstate_capi(PyObject *self, PyObject *Py_UNUSED(args))
     Py_RETURN_NONE;
 }
 
-static PyObject *
-frame_getlocals(PyObject *self, PyObject *frame)
-{
-    if (!PyFrame_Check(frame)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
-        return NULL;
-    }
-    return PyFrame_GetLocals((PyFrameObject *)frame);
-}
-
-static PyObject *
-frame_getglobals(PyObject *self, PyObject *frame)
-{
-    if (!PyFrame_Check(frame)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
-        return NULL;
-    }
-    return PyFrame_GetGlobals((PyFrameObject *)frame);
-}
-
-static PyObject *
-frame_getgenerator(PyObject *self, PyObject *frame)
-{
-    if (!PyFrame_Check(frame)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
-        return NULL;
-    }
-    return PyFrame_GetGenerator((PyFrameObject *)frame);
-}
-
-static PyObject *
-frame_getbuiltins(PyObject *self, PyObject *frame)
-{
-    if (!PyFrame_Check(frame)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
-        return NULL;
-    }
-    return PyFrame_GetBuiltins((PyFrameObject *)frame);
-}
-
-static PyObject *
-frame_getlasti(PyObject *self, PyObject *frame)
-{
-    if (!PyFrame_Check(frame)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
-        return NULL;
-    }
-    int lasti = PyFrame_GetLasti((PyFrameObject *)frame);
-    if (lasti < 0) {
-        assert(lasti == -1);
-        Py_RETURN_NONE;
-    }
-    return PyLong_FromLong(lasti);
-}
-
-static PyObject *
-frame_new(PyObject *self, PyObject *args)
-{
-    PyObject *code, *globals, *locals;
-    if (!PyArg_ParseTuple(args, "OOO", &code, &globals, &locals)) {
-        return NULL;
-    }
-    if (!PyCode_Check(code)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a code object");
-        return NULL;
-    }
-    PyThreadState *tstate = PyThreadState_Get();
-
-    return (PyObject *)PyFrame_New(tstate, (PyCodeObject *)code, globals, locals);
-}
-
-static PyObject *
-test_frame_getvar(PyObject *self, PyObject *args)
-{
-    PyObject *frame, *name;
-    if (!PyArg_ParseTuple(args, "OO", &frame, &name)) {
-        return NULL;
-    }
-    if (!PyFrame_Check(frame)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
-        return NULL;
-    }
-
-    return PyFrame_GetVar((PyFrameObject *)frame, name);
-}
-
-static PyObject *
-test_frame_getvarstring(PyObject *self, PyObject *args)
-{
-    PyObject *frame;
-    const char *name;
-    if (!PyArg_ParseTuple(args, "Oy", &frame, &name)) {
-        return NULL;
-    }
-    if (!PyFrame_Check(frame)) {
-        PyErr_SetString(PyExc_TypeError, "argument must be a frame");
-        return NULL;
-    }
-
-    return PyFrame_GetVarString((PyFrameObject *)frame, name);
-}
-
-
 static PyObject *
 gen_get_code(PyObject *self, PyObject *gen)
 {
@@ -3599,14 +3496,6 @@ static PyMethodDef TestMethods[] = {
     {"type_get_tp_mro", type_get_tp_mro, METH_O},
     {"get_basic_static_type", get_basic_static_type, METH_VARARGS, NULL},
     {"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
-    {"frame_getlocals", frame_getlocals, METH_O, NULL},
-    {"frame_getglobals", frame_getglobals, METH_O, NULL},
-    {"frame_getgenerator", frame_getgenerator, METH_O, NULL},
-    {"frame_getbuiltins", frame_getbuiltins, METH_O, NULL},
-    {"frame_getlasti", frame_getlasti, METH_O, NULL},
-    {"frame_new", frame_new, METH_VARARGS, NULL},
-    {"frame_getvar", test_frame_getvar, METH_VARARGS, NULL},
-    {"frame_getvarstring", test_frame_getvarstring, METH_VARARGS, NULL},
     {"gen_get_code", gen_get_code, METH_O, NULL},
     {"get_feature_macros", get_feature_macros, METH_NOARGS, NULL},
     {"test_code_api", test_code_api, METH_NOARGS, NULL},
@@ -4404,6 +4293,9 @@ PyInit__testcapi(void)
     if (_PyTestCapi_Init_Import(m) < 0) {
         return NULL;
     }
+    if (_PyTestCapi_Init_Frame(m) < 0) {
+        return NULL;
+    }
 
     PyState_AddModule(m, &_testcapimodule);
     return m;
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index 733bb69acb16e2..e94b4c46e6dbb8 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -128,6 +128,7 @@
     
     
     
+    
   
   
     
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index e8ddd537674a9c..782f91afb1996f 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -117,6 +117,9 @@
     
       Source Files
     
+    
+      Source Files
+