Тестирование графического интерфейса пользователя DUnit: могу ли я принудительно преобразовать «приложение» в другую форму?

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

Проблема возникает, когда приложение пытается создать фрейм динамически. Чтение ресурса фрейма доходит до того, что ему нужен дескриптор окна (в моем случае установка заголовка вкладки). Здесь он переходит от TWinControl.GetHandle к TWinControl.CreateWnd и к TCustomFrame.CreateParams.

В этом CreateParams код говорит:

  if Parent = nil then
    Params.WndParent := Application.Handle;

Вот где и проявляется разница. Когда я запускаю реальное приложение (не в тесте), Application.Handle здесь возвращает ненулевое число, и поток продолжается нормально. Но в тестовом приложении DUnit Application.Handle здесь возвращает 0. Это приводит к тому, что код в TWinControl.CreateWnd вызывает исключение, сообщающее, что у кадра нет родителя:

  with Params do
  begin
    if (WndParent = 0) and (Style and WS_CHILD <> 0) then
      if (Owner <> nil) and (csReading in Owner.ComponentState) and
        (Owner is TWinControl) then
        WndParent := TWinControl(Owner).Handle
      else
        raise EInvalidOperation.CreateFmt(SParentRequired, [Name]);

Я бы хотел попытаться обойти эту проблему (и в целом все тестовые задачи), не изменяя "производственный" код только из-за тестов. Не могли бы вы подсказать, могу ли я каким-то образом принудить «Приложение» к чему-то другому или каким-то другим способом обойти это?

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


person DelphiUser    schedule 22.02.2012    source источник
comment
У вашего dpr есть Application.Initialize? Может быть, это устанавливает ручку.   -  person mjn    schedule 22.02.2012
comment
И мое тестовое приложение, и реальное приложение имеют Application.Initialize в своих dprs. Я могу попробовать пошагово выполнить инициализацию, чтобы увидеть, смогу ли я найти место, где Application.Handle установлено на что-то или нет.   -  person DelphiUser    schedule 22.02.2012
comment
DUnit мне тоже кажется не лучшим вариантом для тестирования графического интерфейса.   -  person GolezTrol    schedule 22.02.2012
comment
Просто отметил возможное простое решение - TApplication.Handle на самом деле является свойством с сеттером, который позволяет его устанавливать. Благодаря этому я мог изменить его на дескриптор формы и пройти первую точку исключения. Неудивительно, что вскоре после этого я столкнулся с другими исключениями. Придется исследовать эти ...   -  person DelphiUser    schedule 22.02.2012
comment
TApplication создает свой дескриптор в своем конструкторе, пока IsLibrary ложно. Это означает, что DUnit либо ошибочно устанавливает IsLibrary, что ваш код находится в DLL, либо DUnit перезаписывает Application.Handle перед запуском вашего кода.   -  person Rob Kennedy    schedule 22.02.2012
comment
Только что заметил две вещи: DUnit работает как apptype = CONSOLE даже при использовании GUITestRunner, и он НЕ вызывает Application.Run в своем DPR. Они могут быть связаны. В любом случае установка свойства Application.Handle для моей основной формы, похоже, решает большую часть начальных проблем. У меня есть некоторые проблемы на этапе выпуска при уничтожении компонентов, но я буду работать с ними.   -  person DelphiUser    schedule 23.02.2012


Ответы (2)


Вместо того, чтобы обойти способ установки Application.Handle, вы должны создать TForm и установить свой frame.parent как этот TForm.

//Dunit Test Scaffolding code...Set up a workable environment for the test:
aForm := TForm.Create(nil);
aFrame := TFrame.Create(aForm);
aFrame.Parent := aForm;

В реальных приложениях у фреймов есть родительский элемент (обычно он связан с окном, TForm или TPanel). Вы пытаетесь указать фрейму работать без родителя, для чего TFrame не предназначен.

person Warren P    schedule 22.02.2012
comment
На самом деле, я не пытаюсь указать фрейму работать без родителя, просто создайте его. В моем реальном приложении фреймы сначала создаются с помощью основной формы в качестве владельца. Позже, когда будут сделаны определенные выборы, в зависимости от выбора выбирается подходящий фрейм, и этот фрейм затем настраивается так, чтобы его родительский элемент был панелью в основной форме. Таким образом, все кадры будут отображаться с одним и тем же родителем, по одному, но созданные заранее. Все это происходит в моем производственном коде, который я не хочу менять только ради тестирования. - person DelphiUser; 23.02.2012
comment
Я не думаю, что TFrame предназначен для того, чтобы делать то, что вы делаете. Вам нужно просто создать и закрепить форму без рамки, и у вас не будет таких проблем. НУЛЕВАЯ ВЫГОДА в изготовлении рамы в вашем случае. - person Warren P; 24.02.2012
comment
При использовании фреймов мне не нужно учитывать какие-либо проблемы с стыковкой, так что это не нулевое преимущество, по крайней мере, для меня. - person DelphiUser; 27.02.2012
comment
Какие проблемы с стыковкой? Одна строка кода: TForm.ManualDock(x,alClient,nil); - person Warren P; 27.02.2012
comment
Что ж, мне бы не понадобилась эта строка кода. И я не знаю, совпадает ли закрепленная форма в графическом интерфейсе пользователя с фреймом (например, пристыкованная форма имеет кнопку закрытия или что-то в этом роде). Кроме того, я мог бы использовать те же кадры и для других вещей, и в этом случае такой подход может быть более проблематичным. И, наконец, я хочу сказать, что у меня есть работающее реальное приложение, которое я не хочу менять из-за требований к тестированию. В любом случае спасибо за интерес и комментарии! - person DelphiUser; 29.02.2012
comment
Я говорю именно о том, что вы беспокоитесь, а не узнаете, и это как раз неправильный технический подход. TForm отлично работает (без полей и родительской по отношению к другой форме или родительскому элементу) и НЕТ проблем, и эта проблема (и ДРУГИЕ ПРОБЛЕМЫ), относящиеся к фреймам, исчезнут, когда вы перестанете использовать фреймы. Фреймы плохо продуманы и проблематичны в вашем случае, как и для многих других приложений. - person Warren P; 29.02.2012

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

У меня есть тестовый класс декоратора, унаследованный от TTestSetup, который содержит ссылку на фиктивную (основную) форму, которую он создает при необходимости.

Я также нашел способ переключить Application.MainForm во время выполнения, используя такой подход: http://www.swissdelphicenter.ch/torry/showcode.php?id=665

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

Затем у меня есть класс тестового примера (унаследованный от TGUITestCase), чьи SetUp и TearDown запускаются для каждого теста. В этом SetUp я создаю основную форму, которую тестирую, а затем устанавливаю ее в качестве основной формы приложения. Затем, после теста в TearDown тестового примера, я снова устанавливаю фиктивную форму в качестве основной формы приложения и только после этого вызова Close and Free для основной формы, которую я тестирую. В противном случае освобождение формы, которая в настоящее время является Application.MainForm, приведет к завершению работы всего приложения DUnit.

person DelphiUser    schedule 23.02.2012