Давайте начнем с примера.
Допустим, у меня есть метод, export
который сильно зависит от схемы БД. И под «сильно зависит» я имею в виду, что я знаю, что добавление нового столбца в определенную таблицу часто (очень часто) приводит к изменению соответствующего export
метода (обычно вы также должны добавить новое поле к данным экспорта).
Программисты часто забывают изменить export
метод, потому что не совсем понятно, стоит ли на это смотреть. Моя цель - заставить программиста явным образом принять решение, чтобы определить, забыл ли он посмотреть на export
метод или просто не хочет добавлять поле в данные экспорта. И я ищу дизайнерское решение для этой проблемы.
У меня есть две идеи, но у них обоих есть недостатки.
Умная оболочка «Читать все»
Я могу создать умную оболочку, которая обеспечивает явное чтение всех данных.
Что-то вроде этого:
def export():
checker = AllReadChecker.new(table_row)
name = checker.get('name')
surname = checker.get('surname')
checker.ignore('age') # explicitly ignore the "age" field
result = [name, surname] # or whatever
checker.check_now() # check all is read
return result
Таким образом, checker
утверждает, что table_row
содержит другие поля, которые не были прочитаны. Но все это выглядит довольно тяжело и (возможно) влияет на производительность.
«Проверьте этот метод»
Я могу просто создать unittest, который запоминает последнюю схему таблицы и завершается ошибкой при каждом изменении таблицы. В этом случае программист увидит что-то вроде «не забудьте проверить export
метод». Чтобы скрыть предупреждение, программист должен (или не хочет - это проблема) проверить export
и вручную (это еще одна проблема) исправить тест, добавив в него новые поля.
У меня есть несколько других идей, но они слишком сложны для реализации или слишком сложны для понимания (и я не хочу, чтобы проект стал загадкой).
Вышеупомянутая проблема - только пример более широкого класса проблем, с которыми я сталкиваюсь время от времени. Я хочу связать некоторые фрагменты кода и / или инфраструктуры, поэтому изменение одного из них немедленно предупреждает программиста о необходимости проверки другого. Обычно у вас есть несколько простых инструментов, таких как извлечение общей логики или написание надежного юнит-теста, но я ищу инструмент для более сложных случаев: возможно, некоторые шаблоны проектирования, о которых я сейчас знаю.
источник
export
на основе схемы?export
есть ли у вас все, что вам реально нужно?Ответы:
Вы идете по правильному пути с вашей идеей модульного тестирования, но ваша реализация неверна.
Если
export
отношение связано со схемой и схема изменилась, возможны два случая:Либо
export
все еще работает отлично, потому что это не было затронуто небольшим изменением схемы,Или это ломается.
В обоих случаях целью сборки является отслеживание этой возможной регрессии. Группа тестов - будь то интеграционные тесты, системные тесты, функциональные тесты или что-то еще - гарантирует, что ваша
export
процедура работает с текущей схемой, независимо от того, изменилась она или нет со времени предыдущего коммита. Если эти испытания пройдут, отлично. Если они терпят неудачу, это признак для разработчика, что он, возможно, что-то упустил, и четкое указание, где искать.Почему ваша реализация неверна? Ну, по нескольким причинам.
Это не имеет ничего общего с юнит-тестами ...
... и, на самом деле, это даже не тест.
Хуже всего то, что исправление «теста» требует, ну, на самом деле, изменения «теста», то есть выполнения операции, которая совершенно не связана с
export
.Вместо этого, делая реальные тесты для
export
процедуры, вы убедитесь, что разработчик исправитexport
.В более общем случае, когда вы сталкиваетесь с ситуацией, когда изменение в одном классе всегда или обычно требует изменения в совершенно другом классе, это является хорошим признаком того, что вы ошиблись в своем дизайне и нарушаете принцип единой ответственности.
Хотя я говорю конкретно о классах, это относится более или менее к другим объектам. Например, изменение схемы базы данных должно либо автоматически отражаться в вашем коде, например, с помощью генераторов кода, используемых многими ORM, либо, по крайней мере, должно быть легко локализовано: если я добавлю
Description
столбец вProduct
таблицу и не буду использовать ORM или генераторы кода, Я, по крайней мере, ожидаю, чтобы сделать один изменение вData.Product
классе DAL без необходимости поиска по всей базе кода и нахождения некоторых вхожденийProduct
класса, скажем, на уровне представления.Если вы не можете разумно ограничить изменение одним местоположением (либо потому, что вы находитесь в случае, когда оно просто не работает, либо потому, что оно требует огромного количества разработки), тогда вы создаете риск регрессий . Когда я меняю класс
A
и классB
где-то в базе кода перестает работать, это регрессия.Тестирование снижает риск регрессий и, что гораздо важнее, показывает местоположение регрессии. Вот почему, когда вы знаете, что изменения в одном месте вызывают проблемы в совершенно другой части базы кода, убедитесь, что у вас достаточно тестов, которые вызывают аварийные сигналы, как только на этом уровне возникает регрессия.
Во всех случаях избегайте полагаться в таких случаях только на комментарии. Что-то вроде:
никогда не работает. Мало того, что разработчики не будут читать это в большинстве случаев, но это часто заканчивается либо удалением, либо удалением от рассматриваемой линии, и становится невозможным для понимания.
источник
If you change the following line...
которая работает автоматически и не может быть просто проигнорирована. Я никогда не видел, чтобы кто-то на самом деле использовал такие ловушки, отсюда и сомнения.B
не перестает работать, возможно , он начинает работать неправильно. Но я не могу предсказать будущее и не могу написать тесты, которые предсказывают будущее, поэтому я пытаюсь напомнить разработчику, чтобы он писал этот новый тест.Для меня это звучит так, как будто твои изменения не указаны. Скажем, вы живете где-то, у кого нет почтовых индексов, поэтому у вас нет столбца почтовых индексов в таблице адресов. Затем вводятся почтовые индексы, или вы начинаете общаться с клиентами, которые живут там, где есть почтовые индексы, и вам нужно добавить этот столбец в таблицу.
Если рабочий элемент просто говорит «добавить столбец почтового индекса в таблицу адресов», то да, экспорт будет прерван или, по крайней мере, не будет экспортировать почтовые индексы. Но как насчет экрана ввода, который используется для ввода почтовых индексов? Отчет, в котором перечислены все клиенты и их адреса? Есть множество вещей, которые нужно изменить, когда вы добавите этот столбец. Работа по запоминанию этих вещей является важной - вы не должны рассчитывать на случайные артефакты кода, чтобы «заманить» разработчиков в запоминание.
Когда принимается решение добавить значимый столбец (т. Е. Не просто какой-то кешированный итог или денормализованный поиск или другое значение, которое не принадлежит ни экспорту, ни отчету, ни экрану ввода), созданные рабочие элементы должны включать ВСЕ изменения необходимо - добавить столбец, обновить скрипт заполнения, обновить тесты, обновить экспорт, отчеты, экраны ввода и так далее. Не все они могут быть назначены (или подобраны) одному и тому же человеку, но все они должны быть выполнены.
Иногда разработчики предпочитают добавлять столбцы сами, как часть реализации более значительных изменений. Например, кто-то, возможно, написал рабочий элемент для добавления чего-либо на экран ввода и в отчет, не задумываясь о том, как это реализовано. Если это часто случается, вам нужно решить, нужно ли вашему сумматору рабочих элементов знать детали реализации (чтобы можно было добавлять все нужные рабочие элементы) или разработчикам нужно знать, что рабочий элемент сумматор иногда пропускает вещи. Если это последнее, вам нужна культура «не просто меняйте схему; остановитесь и подумайте о том, что еще влияет».
Если бы было много разработчиков, и это происходило более одного раза, я бы настроил оповещение о повторной регистрации для руководителя группы или другого старшего сотрудника, который будет предупрежден об изменениях схемы. Затем этот человек мог бы искать связанные рабочие элементы, чтобы справиться с последствиями их изменения схемы, и, если рабочие элементы отсутствовали, он мог не только добавить их, но и обучить того, кто их исключил из плана.
источник
Почти всегда при создании экспорта я также создаю соответствующий импорт. Поскольку у меня есть другие тесты, которые полностью заполняют экспортируемую структуру данных, я могу затем построить двусторонний модульный тест, который сравнивает полностью заполненный оригинал с экспортированной и импортированной копией. Если они одинаковы, то экспорт / импорт завершен; если они не совпадают, то модульный тест не пройден, и я знаю, что механизм экспорта нуждается в обновлении.
источник