Поблочное тестирование большие блоки кода (отображения, перевод, и т.д.)

Мы тест единицы, большая часть нашей бизнес-логики, но застревают о том, как лучше всего протестировать некоторые наши большие сервисные задачи и стандартные программы импорта/экспорта. Например, рассмотрите экспорт данных платежной ведомости от одной системы до сторонней системы. Для экспорта данных в формате потребности компании мы должны поразить ~40 таблиц, который создает ситуацию с кошмаром для создания данных тестирования и насмешки зависимости.

Например, рассмотрите следующее (подмножество ~3500 строк кода экспорта):

public void ExportPaychecks()
{
   var pays = _pays.GetPaysForCurrentDate();
   foreach (PayObject pay in pays)
   {
      WriteHeaderRow(pay);
      if (pay.IsFirstCheck)
      {
         WriteDetailRowType1(pay);
      }
   }
}

private void WriteHeaderRow(PayObject pay)
{
   //do lots more stuff
}

private void WriteDetailRowType1(PayObject pay)
{
   //do lots more stuff
}

У нас только есть один открытый метод в этом конкретном классе экспорта - ExportPaychecks (). Это - действительно единственное действие, которое имеет любой смысл кому-то называющему этот класс... все остальное является частным (~80 закрытых функций). Мы могли обнародовать их для тестирования, но тогда мы должны будем дразнить их для тестирования каждого отдельно (т.е. Вы не можете протестировать ExportPaychecks в вакууме, не дразня функцию WriteHeaderRow. Это - огромная боль также.

Так как это - единственный экспорт для единственного поставщика, движущаяся логика в Домен не имеет смысла. Логика не имеет никакого доменного значения за пределами этого конкретного класса. Как тест, мы пристроили модульные тесты, которые имели близко к 100%-му покрытию кода..., но это потребовало безумной суммы данных тестирования, введенных в тупик/фиктивные объекты плюс более чем 7 000 строк кода из-за блокирования того, чтобы/дразнить наши много зависимостей.

Как производитель программного обеспечения HRIS, у нас есть сотни экспорта и импорта. Другие компании ДЕЙСТВИТЕЛЬНО модульный тест этот тип вещи? Если так, есть ли какие-либо ярлыки для создания этого менее болезненным? Я наполовину заставлен не сказать "поблочное тестирование стандартные программы импорта/экспорта" и просто реализовать интеграционное тестирование позже.

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

28
задан Andrew 18 January 2010 в 06:45
поделиться

10 ответов

Это одна из тех областей, где концепция издевательства всего падает. Конечно, тестирование каждая метода в изоляции будет «лучшим» способом поступления вещей, но сравнивать усилия о создании тестовых версий всех ваших методов для указания кода в тестовой базе данных (сброс в начале каждого теста, при необходимости ).

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

2
ответ дан 28 November 2019 в 03:23
поделиться

Добавьте этот маршрут к нижней части файла config/routes.rb (над ним должны быть перечислены более конкретные маршруты):

map.sitemap '/sitemap.xml', :controller => 'sitemap'

Создайте SitemapController (app/controllers/sitemap_controller):

class SitemapController < ApplicationController
  layout nil

  def index
    headers['Content-Type'] = 'application/xml'
    last_post = Post.last
    if stale?(:etag => last_post, :last_modified => last_post.updated_at.utc)
      respond_to do |format|
        format.xml { @posts = Post.sitemap } # sitemap is a named scope
      end
    end
  end
end

- Как вы видите, это для блога, поэтому используйте модель Post . Это шаблон представления HAML (app/views/sitemap/index.xml.haml):

- base_url = "http://#{request.host_with_port}"
!!! XML
%urlset{:xmlns => "http://www.sitemaps.org/schemas/sitemap/0.9"}
  - for post in @posts
    %url
      %loc #{base_url}#{post.permalink}
      %lastmod=post.last_modified
      %changefreq monthly
      %priority 0.5

Все! Вы можете протестировать его, вызвав http ://localhost: 3000/sitemap.xml (при использовании Mongrel) в браузере или, возможно, с помощью ЗАВИТКА.

Обратите внимание, что контроллер использует метод stale? для выдачи ответа HTTP 304 Not Modified, если нет новых сообщений с момента последнего запроса карты сайта.

-121--961303-

Безопасность типа является одной из причин.

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

AddItem(HttpMethod.POST, ProductEntry.class),
-121--2180536-

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

  1. На уровне метода я тестирую все, что субъективно считаю «сложным». Это включает в себя 100% исправлений ошибок, плюс все, что просто заставляет меня нервничать.

  2. На уровне модуля я тестирую основные сценарии использования. Как вы уже сталкивались, это довольно болезненно, поскольку это требует как-то насмешки над данными. Я выполнил это путем абстрагирования интерфейсов базы данных (т.е. отсутствие прямых соединений SQL в модуле отчетности). Для некоторых простых тестов я ввел тестовые данные вручную, для других я написал интерфейс базы данных, который записывает и/или воспроизводит запросы, чтобы я мог загрузить свои тесты с реальными данными. Другими словами, я запускаю один раз в режиме записи, и он не только извлекает реальные данные, но и сохраняет снимок для меня в файле; Когда я запускаю в режиме воспроизведения, он обращается к этому файлу вместо реальных таблиц базы данных. (Я уверен, что есть насмешливые рамки, которые могут это сделать, но так как каждое взаимодействие SQL в моем мире имеет подпись Вызов хранимой процедуры - > Набор записей было довольно просто просто написать его сам.)

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

1
ответ дан 28 November 2019 в 03:23
поделиться

У меня нет ничего общего с C #, Но у меня есть идея, которую вы могли бы попробовать здесь. Если вы немного разделите свой код, то вы заметите, что то, что у вас есть, является в основном цепочка операций, выполненных на последовательностях.

Первый получает оплату за текущую дату:

    var pays = _pays.GetPaysForCurrentDate();

Второй одно безоговорочно обрабатывает результат

    foreach (PayObject pay in pays)
    {
       WriteHeaderRow(pay);
    }

Третий выполняет условную обработку:

    foreach (PayObject pay in pays)
    {
       if (pay.IsFirstCheck)
       {
          WriteDetailRowType1(pay);
       }
    }

Теперь вы можете сделать эти этапы более универсальными (извините за псевдокод, я не Знайте C #):

    var all_pays = _pays.GetAll();

    var pwcdate = filter_pays(all_pays, current_date()) // filter_pays could also be made more generic, able to filter any sequence

    var pwcdate_ann =  annotate_with_header_row(pwcdate);       

    var pwcdate_ann_fc =  filter_first_check_only(pwcdate_annotated);  

    var pwcdate_ann_fc_ann =  annotate_with_detail_row(pwcdate_ann_fc);   // this could be made more generic, able to annotate with arbitrary row passed as parameter

    (Etc.)

Как вы можете видеть, теперь у вас есть набор нескрытых этапов, которые могут быть тестированы отдельно, а затем соединены вместе в произвольном порядке. Такое соединение или композиция, также можно проверить отдельно. И так далее (то есть. - Вы можете выбрать, что тестировать)

3
ответ дан 28 November 2019 в 03:23
поделиться

Я думаю, что Tomasz Zielinski имеет кусок ответ. Но если вы говорите, у вас есть 3500 строк процедурных кодов, то проблема больше, чем это. Резка его в большего количества функций не поможет вам проверить его. Тем не менее, он «первый шаг для определения обязанностей, которые могут быть извлечены в дополнение к другому классу (если у вас есть хорошие имена для методов, которые могут быть очевидны в некоторых случаях).

Я предполагаю, что с таким классом у вас есть невероятный список зависимостей для решения только для того, чтобы можно было улучшить этот класс в тесте. Тогда становится действительно трудно создать экземпляр этого класса в тесте ... Книга из Майкла перьев «Работа с наследием», отвечает очень хорошо такие вопросы. Первая цель сможет хорошо проверить, что код должен быть идентифицировать роли класса и сломать его на более мелкие классы. Конечно, это легко сказать, и ирония заключается в том, что это рискованно обойтись без тестов для обеспечения ваших модификаций ...

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

Опять книга из Майкла перьев, кажется, должна прочитать для вас :) http://www.amazon.com/working-effective-legacy-michael-feathers/dp/0131177052

Добавлен пример:

Этот пример приходит из книги из Майкла перьев и хорошо иллюстрирует вашу проблему I Думаю:

RuleParser  
public evaluate(string)  
private brachingExpression  
private causalExpression  
private variableExpression  
private valueExpression  
private nextTerm()  
private hasMoreTerms()   
public addVariables()  

Обвиольсы здесь, не имеет смысла делать методы Nextterm и Hasmoreterms публики. Никто не должен видеть эти методы, то, как мы переходим к следующему пункту, определенно внутреннее в классе. Итак, как проверить эту логику ??

Хорошо, если вы видите, что это отдельная ответственность и извлекает класс, например, токенизатор. Этот метод внезапно будет публично в этом новом классе! потому что это его цель. Тогда легко проверить это поведение ...

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

Это немного трудно рассуждать по коду, я не могу прочитать. У вас есть другие проблемы, которые помешают вам это сделать, но это моя лучшая попытка.

Надеюсь, это поможет Удачи :)

2
ответ дан 28 November 2019 в 03:23
поделиться

Фракталы используются в финансах для анализа цен на акции. Используются также при изучении сложных систем (теория сложности) и в искусстве .

-121--1307040-

Несколько лет назад я обнаружил, что происходит, если я сделал свои функции короткими :

  • я мог понять их. Мой мозг маленький, и длинные функции не подходят.

  • Классы усложняются (множество функций). Но Extract Class производил небольшие, сплоченные, одноцелевые классы. Опять же, небольшой мозг, поэтому небольшие классы требовали.

  • Число переменных в функции (или классе) невелико. Запоминание того, что от времени объявления до времени использования легко, потому что расстояние невелико .

  • Число областей в моих функциях невелико, поэтому мне не нужно выяснять, куда идут переменные.

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

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

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

-121--2422241-

Просмотрели ли Вы Мок?

Цитату с сайта:

Мок (произносится «Макет-ты» или просто «Mock») - единственная издевательская библиотека для .NET, разработанных с нуля в полной мере использовать преимущества .NET 3.5 (т. е. деревья выражений Linq) и C # 3.0 элементы (то есть лямбда-выражения) которые делают его наиболее продуктивным, тип-безопасный и рефакторинг-удобный доступна издевательская библиотека.

0
ответ дан 28 November 2019 в 03:23
поделиться

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

После того, как у вас есть эта североданная сеть, вы можете начать рефакторировать код, чтобы быть более ремонтом и внедряющимися модульными тестами.

Как упомянуто SERBRECH CAREDICING, эффективно с наследием кодом поможет вам не закончить, я решительно советую прочитать его даже для проектов Greenfield.

http://www.amazon.com/working-effective-legacy-michael-feathers/dp/0131177052

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

6
ответ дан 28 November 2019 в 03:23
поделиться
select m.* 
from MyTable m
inner join (
    select UserID, max(Counter) as MaxCounter
    from MyTable
    group by UserID
) mm on m.UserID = mm.UserID and m.Counter = mm.MaxCounter
-121--4244811-

Пара примечаний в ответ и/или в дополнение к вышесказанному..

Во-первых, если вы не хотите связываться с ActivePerl, Strawberry Perl потрясающе работает для этого.

Во-вторых, вместо использования do _ ms .bat, я бы рекомендовал предпочесть do _ masm , если это возможно, поскольку, согласно INSTALL.W32,

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

Также, сборка 0,9 .8l (L) OpenSSL была кошмаром, поэтому я в конечном итоге сдался и вернулся к 0,9 .8k , который был построен и связан (статически) с libcurl 1,9 без проблем.

-121--1160434-

Что-то общее, что пришло мне в голову о рефакторинге :

Рефакторинг не означает, что вы берете ваш 3 .5k LOC и разделяете его на n частей. Я не рекомендую делать некоторые из ваших 80 методов общедоступными или подобными. Это больше похоже на вертикальное разрезание кода:

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

В результате вам не придется писать единичные тесты, которые охватывают все 3.5k LOC вообще. Только небольшие части покрываются одним тестом, и у вас будет много небольших тестов, независимых друг от друга.


EDIT

Вот хороший список узоров рефакторинга . Среди них довольно хорошо видно мое намерение: Разложить условное .

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

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

7
ответ дан 28 November 2019 в 03:23
поделиться

Мне действительно трудно принять, что у вас есть несколько функций экспорта данных ~ 3,5 KLINES с без общей функциональности вообще между ними. Если это на самом деле случай, то, возможно, тестирование подразделения не то, что вы должны смотреть здесь. Если на самом деле есть только одно, что делает каждый экспортный модуль, и он по существу неделимо, то, возможно, снимка-сравнение, тестовый люкс для интеграции данных, управляемый данными, это то, что называется.

Если есть общие биты функциональности, затем извлеките каждого из них (как отдельные классы) и проверьте их индивидуально. Эти маленькие вспомогательные классы, естественно, будут иметь разные общественные интерфейсы, которые должны снизить проблему частных API, которые не могут быть проверены.

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

public void ExportPaychecks(HeaderFormatter h, CheckRowFormatter f)
{
   var pays = _pays.GetPaysForCurrentDate();
   foreach (PayObject pay in pays)
   {
      h.formatHeader(pay);
      f.WriteDetailRow(pay);
   }
}

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

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


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

2
ответ дан 28 November 2019 в 03:23
поделиться

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

Концерн с вашими тестами было количество поддельных данных, которые вы должны были создать. Вы можете уменьшить это, создав общий прибор ( http://xunitpatterns.com/shared%20fixture.html ). Для модулей тестирует крепеж, который может представлять собой в памяти представление бизнес-объектов для экспорта, или для случая по тестам интеграции, это могут быть фактические базы данных инициализированные с известными данными. Точка заключается в том, что, однако, вы генерируете общий прибор, одинаковы в каждом тесте, поэтому создание новых тестов - это просто вопрос выполнения несовершеннолетних настроек к существующему приспособлению для запуска кода, который вы хотите проверить.

Так следует использовать интеграционные тесты? Один барьер - это то, как настроить общее приспособление. Если вы можете где-то продублировать базы данных, вы можете использовать что-то вроде DBUNIT для подготовки общего прибора. Может быть легче сломать код на куски (импорт, преобразование, экспорт). Затем используйте тесты на основе DBUNIT для проверки импорта и экспорта и экспорта и используют обычные модульные тесты для проверки шага преобразования. Если вы этого сделаете, вам не нужен DBUNIT для настройки общего прибора к этапу преобразования. Если вы можете сломать код на 3 этапа (экстракт, преобразование, экспорт), по крайней мере, вы можете сосредоточить свои усилия по тестированию со стороны, которые могут иметь ошибки или изменения позже.

4
ответ дан 28 November 2019 в 03:23
поделиться

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

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

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

Что часто случается, когда вы разделяете один метод в сложном API, так это то, что вы также получаете большую дополнительную гибкость. То, что начиналось как разовый проект, может превратиться в многоразовую библиотеку.


Вот некоторые мысли о том, как выполнить рефакторинг для рассматриваемой проблемы: Каждое приложение ETL должно выполнить как минимум эти три шага:

  1. Извлечь данные из источника
  2. Преобразовать данные
  3. Загрузить данные в место назначения

(отсюда и название ETL ).Для начала рефакторинга это дает нам по крайней мере три класса с разными обязанностями: Extractor , Transformer и Loader . Теперь вместо одного большого класса у вас есть три с более конкретными обязанностями. Ничего беспорядочного в этом нет, и это уже немного более проверено.

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

  • По крайней мере, вам понадобится хорошее представление в памяти каждой «строки» исходных данных. Если источником является реляционная база данных, вы можете захотеть использовать ORM, но если нет, такие классы необходимо смоделировать так, чтобы они правильно защищали инварианты каждой строки (например, если поле не допускает значения NULL, класс должен гарантировать это путем выдачи исключения при попытке получить нулевое значение). Такие классы имеют четко определенную цель и могут быть протестированы изолированно.
  • То же самое и с местом назначения: для этого вам нужна хорошая объектная модель.
  • Если в источнике выполняется расширенная фильтрация на стороне приложения, вы можете рассмотреть возможность реализации этого с помощью шаблона проектирования Спецификации . Они также очень хорошо поддаются проверке.
  • На этапе преобразования происходит много действий, но теперь, когда у вас есть хорошие объектные модели как источника, так и места назначения, преобразование может выполняться с помощью Mappers - снова тестируемых классов.

Если у вас много «строк» ​​исходных и целевых данных, вы можете дополнительно разделить их в Mappers для каждой логической «строки» и т. Д.

Она никогда не должна становиться беспорядочной, а дополнительное преимущество (помимо автоматизированного тестирования) состоит в том, что объектная модель теперь стала более гибкой. Если вам когда-нибудь понадобится написать другое ETL-приложение с участием одной из двух сторон, вы уже написали хотя бы треть кода.

18
ответ дан 28 November 2019 в 03:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: