Just to clarify, i’m not saying that it is coupled to the test_framework, but that it is coupled to the config.
For a followup I could see something like the code below, for this PR I like that it is more or less a code move.
Rough example; not ment to be a nit:
 0def get_binary_paths(bin_path, exe_ext):
 1    """Get paths of all binaries from environment variables or their default values"""
 2
 3    paths = types.SimpleNamespace()
 4    binaries = {
 5        "bitcoin": "BITCOIN_BIN",
 6        "bitcoind": "BITCOIND",
 7        "bitcoin-cli": "BITCOINCLI",
 8        "bitcoin-util": "BITCOINUTIL",
 9        "bitcoin-tx": "BITCOINTX",
10        "bitcoin-chainstate": "BITCOINCHAINSTATE",
11        "bitcoin-wallet": "BITCOINWALLET",
12    }
13    # Set paths to bitcoin core binaries allowing overrides with environment
14    # variables.
15    for binary, env_variable_name in binaries.items():
16        default_filename = os.path.join(
17            bin_path,
18            binary + exe_ext,
19        )
20        setattr(paths, env_variable_name.lower(), os.getenv(env_variable_name, default=default_filename))