Тест может не зависить от реализации, только если он чисто state-based. Чисто функциональные/приемочные, интеграционные тесты создают бОльшую часть окружения и по этому выглядят по большей части state-based test. Отсюда blackbox testing и прочие разговоры про полную заменимость реализации. Это действительно так. Но обеспечить 100% покрытие такими тестами очень сложно, так как по совокупности они охватывают большой алгоритмический объем -> математическая сложность при 100% покрытии просто по человечески нереализуема. Прибегают ко всяким хитростям, например перекрестный охват кейсов, то есть когда один тест тестирует кучу кейсов. Здесь есть негативная побочка - поскольку тесты не локализуют четко кейс, в будущем на них всегда тратится куча времени: при возврате к тесту полдня курим - что же тут вообще происходит?!
С модульными тестами все по другому. Модульные тесты легко писать и они хорошо локализуют проблемы. При полном покрытии практически навсегда избавляемся от ситуации: здесь исправили, там отвалилось. И в этом смысл модульных тестов - помогать команде работь фиксить/менять/ломать со спокойной душой. Модульные тесты функциональные лишь от части. И state-based для модульных можно использовать в ограниченном количестве случаев, например, когда ведется работа с примитивами/entities/value object и т.п. В случаях, когда юнит взаимодействует с другими подсистемами и классами, не всегда эффективно использовать тестирование состояния. Если стремиться к этому, тесты будут получаться адски пухлыми, непонятными, медленными завязанными на всякую инфраструктуру типа ObjectMother/Builder и т.п. Что бы этого избежать, нужно использовать behaviour-based через моки или стабы. Как только мы вводим использование тестирования поведения - можно сразу забыть про независимость модульных тестов от реализации. По этому надо придерживаться не крайностей, а золотой середины: behaviour/state = 50/50.