diff --git a/Documentation/testing.md b/Documentation/testing.md
new file mode 100644
index 000000000000..218dc36be478
--- /dev/null
+++ b/Documentation/testing.md
@@ -0,0 +1,126 @@
+# Testing in rdma-core
+
+rdma-core now offers an infrastructure for quick and easy additions of feature-
+specific tests.
+
+## Design
+### Resources Management
+`BaseResources` class is the basic objects aggregator available. It includes a
+Context and a PD.
+Inheriting from it is `TrafficResources` class, which also holds a MR, CQ and
+QP, making it enough to support loopback traffic testing. It exposes methods for
+creation of these objects which can be overridden by inheriting classes.
+Inheriting from `TrafficResources` are currently two class:
+- `RCResources`
+- `UDResources`
+
+Both add traffic-specific constants. `UDResources` for example overrides
+create_mr and adds the size of the GRH header to the message size. `RCResources`
+exposes a wrapper to modify the QP to RTS.
+
+### Tests-related Classes
+`unittest.TestCase` is a logical test unit in Python's unittest module.
+`RDMATestCase` inherits from it and adds the option to accept parameters
+(example will follow below) or use a random set of valid parameters:
+- If no device was provided, it iterates over the existing devices, for each
+  port of each device, it checks which GID indexes are valid (in RoCE, only
+  IPv4 and IPv6 based GIDs are used). Each <dev, port, gid> is added to an array
+  and one entry is selected.
+- If a device was provided, the same process is done for all ports of this
+  device, and so on.
+
+### Traffic Utilities
+tests/utils.py offers a few wrappers for common traffic operations, making the
+use of default values even shorter. Those traffic utilities accept an
+aggregation object as their first parameter and rely on that object to have
+valid RDMA resources for proper functioning.
+- get_[send, recv]_wr() creates a [Send, Recv]WR object with a single SGE. It
+  also sets the MR content to be 'c's for client side or 's's for server side
+  (this is later validated).
+- post_send() posts a single send request to the aggregation object's QP. If the
+  QP is a UD QP, an address vector will be added to the send WR.
+- post_recv() posts the given RecvWR <num> times, so it can be used to fill the
+  RQ prior to traffic as well as during traffic.
+- poll_cq() polls <num> completions from the CQ and raises an exception on a
+  non-success status.
+- validate() verifies that the data in the MR is as expected ('c's for server,
+  's's for client).
+- traffic() runs <num> iterations of send/recv between 2 players.
+
+## How to run rdma-core's tests
+The tests are not a Python package, as such they can be found under
+/usr/share/doc/rdma-core-{version}.
+In order to run all tests:
+```
+cd /usr/share/doc/rdma-core-26.0
+python3 run_tests.py
+```
+Output will be something like:
+```
+$ python3 run_tests.py
+..........................................ss...............
+----------------------------------------------------------------------
+Ran 59 tests in 13.268s
+
+OK (skipped=2)
+```
+A dot represents a passing test. 's' means a skipped test. 'E' means a test
+that failed.
+
+Tests can also be executed in verbose mode:
+```
+$ python3 run_tests.py -v
+test_create_ah (test_addr.AHTest) ... ok
+test_create_ah_roce (test_addr.AHTest) ... ok
+test_destroy_ah (test_addr.AHTest) ... ok
+test_create_comp_channel (test_cq.CCTest) ... ok
+< many more lines here>
+test_odp_rc_traffic (test_odp.OdpTestCase) ... skipped 'No dev/port/GID combinations, please check your setup and try again'
+test_odp_ud_traffic (test_odp.OdpTestCase) ... skipped 'No dev/port/GID combinations, please check your setup and try again'
+<more lines>
+
+----------------------------------------------------------------------
+Ran 59 tests in 12.857s
+
+OK (skipped=2)
+```
+Verbose mode provides the reason for skipping the test (if one was provided by
+the test developer).
+
+### Customized Execution
+tests/__init__.py defines a `load_tests` function that returns a
+unittest.TestSuite with the tests that will be executed.
+The default implementation collects all test_* methods from all the classes that
+inherit from `unittest.TestCase` (or `RDMATestCase`) and located in files under
+tests directory which names starts with test_.
+Users can replace that and create a suite that contains only a few selected
+tests:
+```
+suite = unittest.TestSuite()
+suite.addTest(RDMATestCase.parametrize(YourTestCase))
+```
+We're using 'parametrize' as it instantiates the TestCase for us.
+'parametrize' can accept arguments as well (device name, IB port, GID index and
+PKey index):
+```
+suite = unittest.TestSuite()
+suite.addTest(RDMATestCase.parametrize(YourTestCase, dev_name='devname'))
+```
+
+## Writing Tests
+The following section explains how to add a new test, using tests/test_odp.py
+as an example. It's a simple test that runs ping-pong over a few different
+traffic types.
+
+ODP requires capability check, so a decorator was added to tests/utils.py.
+The first change for ODP execution is when registering a memory region (need to
+set the ON_DEMAND access flag), so we do as follows:
+1. Create the players by inheriting from `RCResources` (for RC traffic).
+2. In the player, override create_mr() and add the decorator to it. It will run
+   before the actual call to ibv_reg_mr and if ODP caps are off, the test will
+   be skipped.
+ 3. Create the `OdpTestCase` by inheriting from `RDMATestCase`.
+ 4. In the test case, add a method starting with test_, to let the unittest
+    infrastructure that this is a test.
+ 5. In the test method, create the players (which already check the ODP caps)
+    and call the traffic() function, providing it the two players.
