Как я могу уменьшить ручную работу по переносу сторонних библиотек на объектную модель большего размера?

16

Как и автор этого вопроса 2012 года, а этот - 2013 года , у меня есть сторонняя библиотека, которую нужно обернуть, чтобы правильно протестировать мое приложение. Верхний ответ гласит:

Вы всегда хотите обернуть сторонние типы и методы за интерфейс. Это может быть утомительно и больно. Иногда вы можете написать генератор кода или использовать инструмент для этого.

В моем случае библиотека предназначена для объектной модели и, следовательно, имеет большее количество классов и методов, которые необходимо обернуть, чтобы эта стратегия была успешной. Помимо просто «утомительного и болезненного», это становится жестким барьером для тестирования.

За 4 года, прошедшие с этого вопроса, я знаю, что рамки изоляции прошли долгий путь. Мой вопрос: существует ли сейчас более простой способ добиться эффекта полной упаковки сторонних библиотек? Как я могу снять боль с этого процесса и уменьшить ручное усилие?


Мой вопрос не является дубликатом вопросов, с которыми я изначально связывался, поскольку мой вопрос касается сокращения ручного усилия по переносу. Эти другие вопросы задают только в том случае, если обертка имеет смысл, а не о том, как можно сдерживать усилия.

Том Райт
источник
О каком языке программирования и о какой библиотеке вы говорите?
Док Браун
@DocBrown C # и библиотека для работы с PDF.
Том Райт
2
Я начал публикацию на meta, чтобы найти поддержку для повторного открытия вашего вопроса.
Док Браун
Спасибо @DocBrown - я надеюсь, что там будут некоторые интересные перспективы.
Том Райт
1
Когда мы не получим лучших ответов в течение следующих 48 часов, я вознагражу их за это.
Док Браун

Ответы:

4

Предполагая, что вы не ищите насмешливые рамки, потому что они ультра-вездесущи и их легко найти , есть несколько вещей, на которые стоит обратить внимание:

  1. Есть «никогда», что вы должны «всегда» делать.
    Не всегда лучше завернуть стороннюю библиотеку. Если ваше приложение по своей природе зависит от библиотеки, или оно буквально построено на одной или двух основных библиотеках, не тратьте свое время на его завершение. Если библиотеки меняются, ваше приложение должно будет измениться в любом случае.
  2. Можно использовать интеграционные тесты.
    Это особенно верно в отношении границ, которые являются стабильными, присущими вашему приложению или не могут быть легко смоделированы. Если эти условия будут выполнены, упаковка и насмешка будут сложными и утомительными. В этом случае я бы избегал и того и другого: не оборачивайся и не издевайся; просто напишите интеграционные тесты. (Если целью является автоматизированное тестирование.)
  3. Инструменты и структура не могут устранить логическую сложность.
    В принципе, инструмент можно вырезать только на шаблоне. Но нет автоматизируемого алгоритма, позволяющего взять сложный интерфейс и сделать его простым - не говоря уже о том, чтобы взять интерфейс X и адаптировать его под свои нужды. (Только вы знаете этот алгоритм!) Итак, хотя есть, несомненно, инструменты, которые могут генерировать тонкие обертки, я бы предположил, что они еще не вездесущи, потому что, в конце концов, вам все равно нужно просто разумно кодировать и, следовательно, вручную, против интерфейса, даже если он скрыт за оболочкой.

Тем не менее, есть тактика, которую вы можете использовать на многих языках, чтобы избежать прямого обращения к классу. А в некоторых случаях вы можете «симулировать» интерфейс или тонкую оболочку, которой на самом деле не существует. В C #, например, я бы пошел одним из двух маршрутов:

  1. Используйте фабричную и неявную типизацию .

Вы можете избежать усилий, чтобы полностью обернуть сложный класс с этим небольшим комбо:

// "factory"
class PdfDocumentFactory {
  public static ExternalPDFLibraryDocument Build() {
    return new ExternalPDFLibraryDocument();
  }
}

// code that uses the factory.
class CoreBusinessEntity {
  public void DoImportantThings() {
    var doc = PdfDocumentFactory.Build();

    // ... i have no idea what your lib does, so, I'm making stuff but.
    // but, you can do whatever you want here without explicitly
    // referring to the library's actual types.
    doc.addHeader("Wee");
    doc.getAllText().makeBiggerBy(4).makeBold().makeItalic();
    return doc.exportBinaryStreamOrSomething();
  }
}

Если вы можете избежать сохранения этих объектов как членов, либо с помощью более «функционального» подхода, либо путем сохранения их в словаре (или чего- либо еще ), этот подход имеет преимущество проверки типов во время компиляции без необходимости точно знать ваши основные бизнес-сущности. с каким классом они работают.

Все, что требуется, это то, что во время компиляции класс, возвращаемый вашей фабрикой, на самом деле имеет методы, которые использует ваш бизнес-объект.

  1. Используйте динамическую типизацию .

Это в том же духе, что и при использовании неявной типизации , но включает другой компромисс: вы теряете проверки типа компиляции и получаете возможность анонимно добавлять внешние зависимости в качестве членов класса и вводить ваши зависимости.

class CoreBusinessEntity {
  dynamic Doc;

  public void InjectDoc(dynamic Doc) {
    Doc = doc;
  }

  public void DoImortantThings() {
    Doc.addHeader("Wee");
    Doc.getAllText().makeBiggerBy(4).makeBold().makeItalic();
    return Doc.exportBinaryStreamOrSomething();
  }
}

При использовании обеих этих тактик, когда приходит время высмеивать ExternalPDFLibraryDocument, как я уже говорил ранее, вам есть над чем работать, но эту работу вам все равно придется выполнять . И с этой конструкцией вы избежали утомительного определения сотен классов тонких маленьких упаковщиков. Вы просто использовали библиотеку, не глядя прямо на нее - по большей части.

Учитывая все вышесказанное, есть три основные причины, по которым я бы по-прежнему рассматривал явное завершение работы сторонней библиотеки - ни одна из которых не намекала бы на использование инструмента или инфраструктуры:

  1. Конкретная библиотека не является неотъемлемой частью приложения.
  2. Было бы очень дорого поменять местами, не оборачивая его.
  3. Мне не нравится сам API.

Если у меня нет какой-либо озабоченности по поводу уровня во всех этих трех областях, вы не приложите каких-либо значительных усилий, чтобы завершить ее. И, если у вас есть проблемы во всех трех областях, автоматически созданная тонкая оболочка не поможет.

Если вы решили обернуть библиотеки вверх, наиболее эффективное и эффективное использование вашего времени , чтобы построить приложение от интерфейса вы хотите ; не против существующего API.

Иными словами, прислушайтесь к классическому совету: отложите каждое решение, которое сможете. Сначала создайте «ядро» вашего приложения. Код против интерфейсов, который в конечном итоге будет делать то, что вы хотите, что в конечном итоге будет выполнено "периферийными устройствами", которые еще не существуют. Устраните пробелы по мере необходимости.

Это усилие может не показаться сэкономленным временем; но если вы чувствуете, что вам нужна обертка, это самый эффективный способ сделать это безопасно.

Думайте об этом так.

Вам нужно кодировать эту библиотеку в каком-то темном углу вашего кода - даже если она обернута. Если вы будете издеваться над библиотекой во время тестирования, это неизбежно приведет к ручным усилиям, даже если она будет завершена. Но это не означает, что вам нужно напрямую подтверждать эту библиотеку по имени во всем объеме вашего приложения.

TLDR

Если библиотека заслуживает того, чтобы ее обернуть, используйте тактику, чтобы избежать широко распространенных прямых ссылок на вашу стороннюю библиотеку, но не используйте ярлыки для создания тонких оболочек. Сначала создайте свою бизнес-логику, продумайте свои интерфейсы и, при необходимости , разрабатывайте свои адаптеры органично .

И, если дело доходит до этого, не бойтесь интеграционных тестов. Они немного размыты, но они все еще предлагают доказательства работы кода, и их все еще легко можно сделать, чтобы держать регрессии в страхе.

svidgen
источник
2
Это еще один пост, не отвечающий на вопрос - который был явно не «когда обернуть», а «как уменьшить ручное усилие».
Док Браун
1
Извините, но я думаю, что вы упустили суть вопроса. Я не понимаю, как ваши предложения могли бы уменьшить любые ручные усилия при упаковке. Предположим, что библиотека lib имеет сложную объектную модель в виде API с несколькими десятками классов и сотнями методов. Его API хорош, как и для стандартного использования, но как обернуть его для модульного тестирования с меньшими усилиями / усилиями?
Док Браун
1
TLDR; если вы хотите получить бонусные баллы, скажите нам что-то, чего мы еще не знаем ;-)
Док Браун
1
@DocBrown Я не упустил момент. Но я думаю, что вы упустили смысл моего ответа. Вложение правильных усилий экономит вам много работы. Есть последствия тестирования - но это только побочный эффект. Использование инструмента для автоматической генерации тонкой обертки вокруг библиотеки все еще оставляет вам возможность создавать свою основную библиотеку на основе чужого API и создавать макеты - это много усилий, которых нельзя избежать, если вы настаиваете на том, чтобы оставить библиотеку вне из ваших тестов.
svidgen
1
@DocBrown Это абсурд. «Есть ли у этого класса проблем класс инструментов или подходов?» Да. Конечно, это так. Защитное кодирование и отсутствие догматичности в модульных тестах ... как говорит мой ответ. Если бы у вас была автоматически сгенерированная тонкая обертка, какое значение это обеспечило бы !? ... Это не позволит вам вводить зависимости для тестирования, вам все равно придется делать это вручную. И это не позволит вам поменять библиотеку, потому что вы все еще программируете на основе API библиотеки ... теперь это просто косвенно.
svidgen
9

Не тестируйте этот код модульно. Напишите интеграционные тесты вместо этого. В некоторых случаях модульного тестирования, насмешливый, является утомительным и болезненным. Откажитесь от модульных тестов и напишите интеграционные тесты, которые фактически вызывают обращения поставщиков.

Обратите внимание, что эти тесты следует запускать после развертывания как автоматическое действие после развертывания. Они не запускаются как часть модульных тестов или процесса сборки.

Иногда интеграционный тест лучше подходит, чем модульный тест в некоторых частях вашего приложения. Обручи, которые нужно пройти, чтобы сделать код «тестируемым», иногда могут быть вредными.

Джон Рейнор
источник
2
Первое, что я подумал, когда прочитал ваш ответ, было «нереально для PDF, так как сравнение файлов на двоичном уровне не показывает, что изменилось или изменение является проблематичным». Затем я нашел этот старый пост SO , указывает, что он действительно может работать (так что +1).
Док Браун
@DocBrown инструмент в ссылке SO не сравнивает PDF в двоичном уровне. Он сравнивает структуру и содержание страниц. Внутренняя структура PDF: safaribooksonline.com/library/view/pdf-explained/9781449321581/…
linuxunil
1
@linuxunil: да, я знаю, как я писал, сначала я подумал ... но потом я нашел решение, упомянутое выше.
Док Браун
о ... я понимаю ... может быть, "это действительно может сработать" в финале твоего ответа меня запутало.
linuxunil
1

Как я понимаю, эта дискуссия фокусируется на возможностях автоматизации создания обертки, а не на обертывании идеи и руководства по реализации. Я постараюсь абстрагироваться от идеи, так как здесь уже есть много об этом.

Я вижу, что мы играем вокруг технологий .NET, поэтому у нас есть мощные возможности отражения. Вы можете рассмотреть:

  1. Инструмент как .NET Wrapper Class Generator . Я не использовал этот инструмент, и я знаю, что он работает на устаревшем технологическом стеке, но, возможно, для вашего случая он подойдет. Конечно, качество кода, поддержка инверсии зависимостей и разделение интерфейсов должны рассматриваться отдельно. Может быть, есть и другие подобные инструменты, но я не стал много искать.
  2. Написание собственного инструмента, который будет искать в ссылочной сборке публичные методы / интерфейсы и выполняет отображение и генерацию кода. Вклад в сообщество будет более чем приветствоваться!
  3. Если .NET не тот случай ... возможно, посмотрите здесь .

Я считаю, что такая автоматизация может представлять собой базовую точку, но не окончательное решение. Ручной рефакторинг кода потребуется ... или в худшем случае редизайн, так как я полностью согласен с тем, что написал svidgen:

Создайте свое приложение на основе интерфейса, который вы хотите; не против стороннего API.

tom3k
источник
Хорошо, по крайней мере, идея, как проблема может быть решена. Но я полагаю, не основываясь на собственном опыте для этого варианта использования?
Док Браун
Вопрос основан на моем собственном опыте в области разработки программного обеспечения (обертка, рефлексия, ди)! Что касается инструментов - я не использовал это, как указано в ответе.
Tom3k
0

Следуйте этим рекомендациям при создании библиотек-оболочек:

  • выставить только небольшое подмножество сторонней библиотеки, которая вам нужна прямо сейчас (и расширить по требованию)
  • держите обертку как можно более тонкой (никакой логики там нет).
Румен Георгиев
источник
1
Просто интересно, почему вы получили 3 отзыва за пост, который не соответствует сути вопроса.
Док Браун