> single lower level unit test but that test will require more frequent maintenance as the code around it evolves due to refactoring.
"more frequent" is not the same as "high maintenance costs" though.
Unit tests should only change when the unit-under-test (sut) changes. Which, for many units is "never". And for some with high churn, indeed, a lot.
Actual and pure e2e tests should never have to change except when the functionality changes.
But all other integration tests most often change whenever one of the components changes. I've had situations where whenever we changed some relation, or added a required-field in our database, we had to manually change hundreds of integration tests and their helpers. "Adding a required field" then became a chore of days of wading through integration tests¹.
With the unit-tests, only one, extremely simple test changed in that case.
With the end-to-end-tests, also, hundreds needed manual changes. But that was because they weren't actual end-to-end tests, and did all sorts of poking around in the database. Worse: that poking-around wasn't abstracted even.
What I'm trying to convey with this example, is that in reality, unit-tests change often if the SUT has a high churn, but that those changes are very local and isolated and simple. Yet, in practice, with integration-tests, the smallest unrelated change to a "unit" has a domino-effect on whole sections of these tests.
(And also that in this example, our E2E were badly designed and terribly executed)
¹Edit: one can imagine the pressure of management to just stop testing.
"more frequent" is not the same as "high maintenance costs" though.
Unit tests should only change when the unit-under-test (sut) changes. Which, for many units is "never". And for some with high churn, indeed, a lot.
Actual and pure e2e tests should never have to change except when the functionality changes.
But all other integration tests most often change whenever one of the components changes. I've had situations where whenever we changed some relation, or added a required-field in our database, we had to manually change hundreds of integration tests and their helpers. "Adding a required field" then became a chore of days of wading through integration tests¹.
With the unit-tests, only one, extremely simple test changed in that case. With the end-to-end-tests, also, hundreds needed manual changes. But that was because they weren't actual end-to-end tests, and did all sorts of poking around in the database. Worse: that poking-around wasn't abstracted even.
What I'm trying to convey with this example, is that in reality, unit-tests change often if the SUT has a high churn, but that those changes are very local and isolated and simple. Yet, in practice, with integration-tests, the smallest unrelated change to a "unit" has a domino-effect on whole sections of these tests. (And also that in this example, our E2E were badly designed and terribly executed)
¹Edit: one can imagine the pressure of management to just stop testing.