Я пытаюсь попрактиковаться в TDD, используя его для разработки простого типа Bit Vector. Я использую Swift, но это не зависит от языка.
My BitVector
- это объект, struct
который хранит один UInt64
и представляет API, который позволяет вам рассматривать его как коллекцию. Детали не имеют большого значения, но все довольно просто. Старшие 57 бит - это биты хранения, а младшие 6 бит - это биты «подсчета», которые сообщают вам, сколько битов хранения на самом деле хранят содержащиеся значения.
Пока у меня есть несколько очень простых возможностей:
- Инициализатор, который создает пустые битовые векторы
count
Свойство типаInt
isEmpty
Свойство типаBool
- Оператор равенства (
==
). NB: это оператор равенства значений, похожий наObject.equals()
Java, а не оператор равенства ссылок, как==
в Java.
Я сталкиваюсь с кучей циклических зависимостей:
Модульный тест, который проверяет мой инициализатор, должен проверить, что он создан заново
BitVector
. Это можно сделать одним из 3 способов:- Проверьте
bv.count == 0
- Проверьте
bv.isEmpty == true
- Проверь это
bv == knownEmptyBitVector
Метод 1 основан на
count
методе 2isEmpty
(который сам по себе полагаетсяcount
, поэтому нет смысла его использовать), метод 3 использует==
. В любом случае, я не могу проверить свой инициализатор изолированно.- Проверьте
Тест
count
должен работать на чем-то, что неизбежно проверяет мой инициализатор (ы)Реализация
isEmpty
опирается наcount
Реализация
==
опирается наcount
.
Я смог частично решить эту проблему, представив частный API, который конструирует BitVector
из существующего битового шаблона (как UInt64
). Это позволило мне инициализировать значения без тестирования каких-либо других инициализаторов, чтобы я мог «загрузиться».
Чтобы мои модульные тесты действительно были модульными тестами, я обнаружил, что выполняю кучу хаков, которые существенно усложняют мой продукт и тестовый код.
Как именно вы справляетесь с такими проблемами?
источник
BitVector
идеально подходит для модульного тестирования и сразу решает ваши проблемы, в которых публичные членыBitVector
нуждаются друг в друге для проведения значимых тестов.Ответы:
Вы слишком беспокоитесь о деталях реализации.
Не важно , что в текущей реализации ,
isEmpty
зависит отcount
(или любых других отношений , вы могли бы): все , что вы должны заботиться о том , что общедоступный интерфейсе. Например, у вас может быть три теста:count == 0
.isEmpty == true
Все это действительные тесты, и они становятся особенно важными, если вы когда-нибудь решите провести рефакторинг внутренних компонентов вашего класса, чтобы в
isEmpty
них была другая реализация, на которую не следует полагатьсяcount
- пока ваши тесты все еще проходят, вы знаете, что не регрессировали что-нибудь.Подобные вещи применимы и к другим вашим пунктам - не забудьте протестировать открытый интерфейс, а не внутреннюю реализацию. Здесь вы можете найти TDD полезным, так как тогда вы будете писать необходимые тесты,
isEmpty
прежде чем вообще напишете какую-либо реализацию для него.источник
Вы пересматриваете свои мысли о том, что такое «модульный тест».
Объект, который управляет изменяемыми данными в памяти, по сути является конечным автоматом. Таким образом, любой ценный вариант использования будет, как минимум, вызывать метод для помещения информации в объект и вызывать метод для считывания копии информации из объекта. В интересных случаях использования вы также будете вызывать дополнительные методы, которые изменяют структуру данных.
На практике это часто выглядит так
или
Терминология «модульного теста» - ну, у нее давняя история не очень хорошего качества.
Кент написал первую версию SUnit в 1994 году , порт для JUnit был в 1998 году, первый проект книги TDD был в начале 2002 года. Путаница имела много времени для распространения.
Основная идея этих тестов (более точно называемых «тестами для программистов» или «тестами для разработчиков») заключается в том, что тесты изолированы друг от друга. Тесты не разделяют какие-либо изменяемые структуры данных, поэтому их можно запускать одновременно. Не беспокойтесь о том, что тесты должны выполняться в определенном порядке, чтобы правильно измерить решение.
Основной вариант использования этих тестов заключается в том, что они выполняются программистом между изменениями в ее собственном исходном коде. Если вы выполняете красно-зеленый протокол рефакторинга, неожиданный КРАСНЫЙ всегда указывает на ошибку в вашем последнем редактировании; вы отменяете это изменение, проверяете, что тесты ЗЕЛЕНЫЕ, и попробуйте снова. Нет большого преимущества в попытках инвестировать в дизайн, где каждая возможная ошибка обнаруживается только одним тестом.
Конечно, если слияние вносит ошибку, то обнаружение, что ошибка больше не является тривиальной. Есть несколько шагов, которые вы можете предпринять, чтобы убедиться, что ошибки легко локализуются. Видеть
источник
В целом (даже если вы не используете TDD) вы должны стараться писать как можно больше тестов, делая вид, что не знаете, как это реализовано.
Если вы на самом деле делаете TDD, это уже должно быть. Ваши тесты являются исполняемой спецификацией программы.
То, как граф вызовов выглядит под тестами, не имеет значения, если сами тесты разумны и хорошо поддерживаются.
Я думаю, что ваша проблема в вашем понимании TDD.
По моему мнению, ваша проблема в том, что вы «смешиваете» своих персонажей TDD. Ваши "тестовые", "кодовые" и "рефакторные" персонажи работают идеально независимо друг от друга, в идеале. В частности, ваши сотрудники, занимающиеся кодированием и рефакторингом, не имеют никаких обязательств перед тестами, за исключением того, чтобы делать / сохранять их работоспособными.
Конечно, в принципе было бы лучше, если бы все тесты были ортогональными и независимыми друг от друга. Но это не касается двух других персонажей TDD, и это определенно не является строгим или даже обязательно реалистичным жестким требованием ваших тестов. По сути: не отбрасывайте свои здравые чувства по поводу качества кода, чтобы попытаться выполнить требование, о котором никто не просит вас.
источник