Why should an automated suite select a deployed target by name rather than carrying its address in the case?
answer
- One suite, many deployments
- Say which, not where
- A symbol the run resolves once
- Count the edits when a host changes
basics
~20 sA named target is one indirection point the run resolves once, so the same suite can be pointed anywhere. An address written into a case pins that case to one deployment and must be found and edited when it moves.
solid answer
~50 sSelecting a target by name means the case says *which* deployment it wants — `local`, `preview`, `prerelease` — and never *where* that deployment is. The run turns that name into the addresses, ports and path prefixes the cases need, and hands them over as one resolved object. Cases read that object; no case contains a literal address. The cost of the alternative is counted in edits. When a deployment moves — a new hostname, a changed port, an added path prefix — a suite that names its target changes one entry, while a suite with addresses in the cases changes every case that carries one, and the ones nobody finds fail with a connection error that reads like an outage. Hardcoded addresses also make a case unrunnable anywhere else, so it never gets run locally before it is pushed.
code
pseudocode · 9 lines# resolved once, before any case runs
name = run_input("target") # "local" | "preview" | "prerelease"
target = TARGET_TABLE[name] # unknown name -> stop the run here, loudly
# a case receives the resolved target; it holds no literal address
case "customer can place an order":
client = open_client(base = target.storefront_address)
result = client.post("/orders", sample_order())
expect result.status is CREATEDgo deeper
Be ready to say what goes wrong when a case contains the address of the system it drives. Know that a run is told which deployment to use by a short name, and that the case is handed resolved addresses rather than looking them up itself.
Explain the two steps — a name supplied to the run, and a lookup that turns it into a resolved target the cases receive — and what each one buys you. Be able to say what the run should do when the name given is unknown.
Show what the absence costs on a real suite: scattered literals, a case that fails like an outage, a case nobody can run locally. Explain how you would migrate a suite that already carries addresses in a hundred cases without stopping the team.
Own this as a standard rather than a preference: one place where deployments are described, cases free of topology, and a run that refuses an unknown target. Be ready to defend the cost of that indirection against the argument that it is only a string.
## One suite, many deployments An automated suite is written once and pointed at many places: a developer's machine, a short-lived preview stack raised for a single change, a shared integration deployment, a pre-release deployment, sometimes a narrow read-only check against the live one. The number of deployments only grows, and each of them moves — a new hostname after a migration, a port change, an added path prefix, a split into two regions. **Target resolution is the step that turns the answer to "which deployment?" into the concrete addresses a run will use.** It happens once, before the first case executes, and it has two parts: 1. The run is handed a **target name** — a short symbol such as `local`, `preview`, `integration`, `prerelease`. This is the only deployment fact anyone types or passes. 2. A lookup turns that symbol into a **resolved target**: an object carrying the base address of each service the suite drives, plus any other attribute that varies with the deployment. Cases are handed the resolved target. They never see the name and they never perform the lookup. How the name reaches the run is a separate question; what matters here is that it is a name and not an address. ## What an address inside a case actually costs The obvious cost is edits, and it is worth setting the two suites side by side. | When this happens | Suite that names its target | Suite with addresses in the cases | |---|---|---| | A hostname changes | one row in the target table | every case that carries that host | | A port or scheme changes | one row | scattered literals, easy to miss | | A path prefix is added | one row | each literal, each spelled slightly differently | | A new deployment appears | add a row and run | copy and edit cases to match | | Someone wants to run one case locally | pass a different name | not possible without editing the case | The less obvious costs are the ones that actually bite: - **The silent survivor.** A migration updates the addresses that somebody's search found. The ones it missed sit in cases that rarely run, and they surface months later to someone who reasonably assumes the deployment is down. - **The false outage.** An address that no longer resolves produces a connection error, and a connection error reads exactly like the product being unavailable. Real time goes into investigating a service that is perfectly healthy. - **The unrunnable case.** A case pinned to a shared deployment cannot be run on a developer's machine before it is pushed, so it gets debugged through the pipeline one commit at a time. - **The two-target run.** Once addresses live per case, nothing forces them to agree. A run that touches two deployments produces a result that describes neither of them. ## What belongs on the resolved target Everything about a deployment that a case might need, and nothing about behaviour: - base addresses for each service or interface the suite drives - a path prefix or tenant identifier where the deployment is partitioned - attributes that describe the deployment rather than the product — a region label, whether it is protected, which capabilities it carries The line to hold is that the resolved target answers **where and what kind**, never **what the product should do**. A value that legitimately differs per deployment is data and belongs on the same object; a rule about how the product behaves belongs in the case. ## Making it stick 1. **Fail on an unknown name.** If the given name is not in the table, stop before the first case and print both what was given and what exists. A default is worse than a stop: a typo silently becomes traffic against a deployment nobody chose, and the results get attributed to the wrong place. 2. **Give cases no way to reach the table.** If a case can read the mapping itself, someone eventually will, and the indirection is gone again. Hand over the resolved object and keep the lookup private to the startup path. 3. **Search for literals as a standing check.** A pattern that matches an address inside a case body is cheap to search for and is a real health number. Drive it to zero and keep it there rather than auditing once. 4. **Migrate from the outside in.** On a suite that already carries addresses, add the table first, resolve it at startup, then replace literals a package at a time. Every literal removed is a case that becomes runnable everywhere. 5. **Name deployments by role, not by machine.** A role name survives a migration; a name derived from a host does not, and it quietly re-teaches the habit you just removed. The idea is small, and it is worth being precise about why it pays: **a case that knows where the product is deployed has taken on a second job, and it will be wrong about that job long before it is wrong about the behaviour it was written to check.**
- What should the run do when it is handed a target name it does not recognise?Stop before any case runs, and say both which name was given and which names exist. Guessing a default is the dangerous option: an unrecognised name that quietly falls back to a shared deployment means someone believes they exercised a preview stack when they hit a shared one, and every result is attributed to the wrong deployment.
- A case needs a deployment-specific tenant identifier, not just an address. Where does that belong?On the resolved target, alongside the addresses. Anything that varies per deployment and is not behaviour belongs on the same object the case is handed, so there is exactly one place to look and one place to change. The moment a second lookup appears inside a case, the suite has two sources of truth about the deployment.
- Is a per-deployment address file in the repository enough, or does the name still matter?The file is where the mapping lives; the name is what selects a row from it, so both are needed. What matters is that the case references neither — it is handed the resolved row. A case that opens the file itself has just re-imported the topology it was meant to be free of.
It is the difference between keeping one contact entry and writing the phone number onto every letter you send: when the number changes, one is an edit and the other is a search.
saying these in an interview costs you the question
- Hardcoding the address and changing it before the release run
- Keeping a copy of the address in each case for readability
- Falling back to a shared deployment when the target name is unknown
- Treating the target name as free text nobody validates
- Assuming a suite that still passes proves every address was updated