In my experience, there is no best practice to test code when it comes to rendering api's, since there are many different factors here:
GPU and CPU vendors (Apple, AMD, Intel), operating systems, drivers.
I agree with Jeshua:
The best practice for testing hardware compatibility is on the actual
hardware in which you are testing compatibility.
There are many useful ways that can make development and testing easier:
You can detect vendors id:
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
NSString* appleGPU = [device.name containsString:@"Apple"];
NSString* intelGPU = [device.name containsString:@"Intel"];
NSString* amdGPU = [device.name containsString:@"AMD"];
NSString* nvidiaGPU = [device.name containsString:@"Nvidia"];
With following method you can find your gpu type:
bool externalGPU = [device isRemovable] == true;
bool integratedGPU = [device isLowPower] == true;
bool discreteGPU = [device isLowPower] == false;
Note:
[device isLowPower];
On a Mac with an Apple silicon M1 chip, the property is NO because the
GPU runs with both high performance and low power.
Determine TBDR GPU architecture:
if (@available(macOS 10.15, *)) {
if ([device supportsFamily: MTLGPUFamilyApple4])
{
// The GPU does support Tile-Based Deferred Rendering technique
}
}
Understand the Managed Mode:
In a unified memory model, a resource with a MTLStorageModeManaged
mode resides in system memory accessible to both the CPU and the GPU.
Behaves like MTLStorageModeShared, has only one copy of content.

Note:
In a unified memory model, Metal may ignore
synchronization calls completely because it only creates a single
memory allocation for the resource.
You can also check some implementations by other developers:
PixarAnimationStudios/USD:
HgiMetalCapabilities::HgiMetalCapabilities(id<MTLDevice> device)
{
if (@available(macOS 10.14.5, ios 12.0, *)) {
_SetFlag(HgiDeviceCapabilitiesBitsConcurrentDispatch, true);
}
defaultStorageMode = MTLResourceStorageModeShared;
bool unifiedMemory = false;
if (@available(macOS 100.100, ios 12.0, *)) {
unifiedMemory = true;
} else if (@available(macOS 10.15, ios 13.0, *)) {
#if defined(ARCH_OS_IOS) || (defined(__MAC_10_15) && __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_15)
unifiedMemory = [device hasUnifiedMemory];
#else
unifiedMemory = [device isLowPower];
#endif
}
_SetFlag(HgiDeviceCapabilitiesBitsUnifiedMemory, unifiedMemory);
#if defined(ARCH_OS_MACOS)
if (!unifiedMemory) {
defaultStorageMode = MTLResourceStorageModeManaged;
}
#endif
}
KhronosGroup/MoltenVK
// Metal Managed:
// - applies to both buffers and textures
// - default mode for textures on macOS
// - two copies of each buffer or texture when discrete memory available
// - convenience of shared mode, performance of private mode
// - on unified systems behaves like shared memory and has only one copy of content
// - when writing, use:
// - buffer didModifyRange:
// - texture replaceRegion:
// - when reading, use:
// - encoder synchronizeResource: followed by
// - cmdbuff waitUntilCompleted (or completion handler)
// - buffer/texture getBytes: