понедельник, 4 ноября 2013 г.

Коротко. О GUI-тестировании "по-русски"

Про реальную разработку написано тут:
http://18delphi.blogspot.ru/2013/10/blog-post_7652.html
http://18delphi.blogspot.ru/2013/10/blog-post_7099.html

Теперь попробую описать собственно идею.

Пусть у нас есть форма:
TSomeForm = class(TForm)
 SomeButton : TButton;
end;//TSomeForm

Пусть мы хотим иметь возможность нажимать на кнопку SomeButton на форме SomeForm.
Из наших тестов.

Этому нам помогут следующие слова тестовой машины:

interface

TkwFindForm = class(TscriptKeyWord)
protected
 procedure DoIt(aContext : TscriptContext); override;
end;//TkwFindForm

TkwFindComponent = class(TscriptKeyWord)
protected
 procedure DoIt(aContext : TscriptContext); override;
end;//TkwFindComponent

TkwButtonClick = class(TscriptKeyWord)
protected
 procedure DoIt(aContext : TscriptContext); override;
end;//TkwButtonClick

И реализация:

implementation

procedure TkwFindForm.DoIt(aContext : TscriptContext);
var
 l_Name : String;
 l_Form : TForm;
begin
 l_Name := aContext.PopString;
 Assert(l_Name <> '');
 l_Form := Screen.FindFormByName(l_Name); // - такого метода нет, но его легко можно написать
 Assert(l_Form <> nil);
 aContext.PushObject(l_Form);
end;

procedure TkwFindComponent.DoIt(aContext : TscriptContext);
var
 l_Name : String;
 l_Form : TForm;
 l_Component : TComponent;
begin
 l_Name := aContext.PopString;
 l_Form := aContext.PopObject As TForm;
 Assert(l_Name <> '');
 Assert(l_Form <> nil);
 l_Component := l_Form.FindComponent(l_Name);
 Assert(l_Component <> nil);
 aContext.PushObject(l_Component);
end;

procedure TkwButtonClick.DoIt(aContext : TscriptContext);
var
 l_Component : TComponent;
begin
 l_Component := aContext.PopObject As TComponent;
 Assert(l_Component Is TButton);
 (l_Component As TButton).Click;
end;

Регистрируем описанные слова в тестовой машине:

initialization
 ScriptEngine.RegisterWord(TkwFindForm, 'FindForm');
 ScriptEngine.RegisterWord(TkwFindComponent, 'FindComponent');
 ScriptEngine.RegisterWord(TkwButtonClick, 'ButtonCick');


Понятное, дело, что идентификаторы слов можно "придумывать" из ClassName, но это - не так важно.

Теперь мы можем написать первый тест:

VAR l_Form
VAR l_Button
'SomeForm' FindForm =: l_Form
l_Form 'SomeButton' FindComponent =: l_Button
l_Button ButtonClick

И он делает то, что хотелось.

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

Но. Хотелось бы "по-русски".

Напишем "вспомогательные" слова. Уже на скриптовом языке тестовой машины.

Пусть мы знаем, что SomeForm, это форма для регистрации пользователя, а SomeButton это кнопка для сохранения.
Ну из бизнес-логики такое следует.

Напишем:

CONST "Форма регистрации" 'SomeForm'
CONST "Сохранить" 'SomeButton'

Тогда тест можно переписать так:

VAR l_Form
VAR l_Button
"Форма регистрации" FindForm =: l_Form
l_Form "Сохранить" FindComponent =: l_Button
l_Button ButtonClick

Ну отчасти "по-русски". Но - не совсем.

Пойдём дальше.

Напишем:

STRING FUNCTION "Найти форму" STRING IN aFormName
 aFormName FindForm =: Result
END //"Найти форму"

STRING FUNCTION "Найти найти компонент на форме" STRING IN aComponentName OBJECT IN aForm
 aForm aComponentName FindComponent =: Result
END //"Найти найти компонент на форме""

PROCEDURE "Нажать кнопку" OBJECT IN aButton
 aButton ButtonClick
END //"Нажать кнопку"

WORDALIAS VAR Переменная

Тогда тест можно переписать так:

Переменная "Найденная форма"
Переменная "Найденная кнопка"
"Найти форму {("Форма регистрации")}" =: "Найденная форма"
"Найти найти компонент {("Сохранить")} на форме {("Найденная форма")}" =: "Найденная кнопка"
"Нажать кнопку {("Найденная кнопка")}"

Ну и последний штрих.
Вносим написанное слово в словарь:

PROCEDURE "Нажать кнопку Сохранить на форме регистрации"
 Переменная "Найденная форма"
 Переменная "Найденная кнопка"
 "Найти форму {("Форма регистрации")}" =: "Найденная форма"
 "Найти найти компонент {("Сохранить")} на форме {("Найденная форма")}" =: "Найденная кнопка"
 "Нажать кнопку {("Найденная кнопка")}"
END //"Нажать кнопку Сохранить на форме регистрации"

Теперь тест приобретает вид:
 "Нажать кнопку Сохранить на форме регистрации"

- все служебные конструкции - "упрятаны" внутрь "слова высокого уровня" - "Нажать кнопку Сохранить на форме регистрации". 

И это уже не совсем "слово", а по сути - целое "предложение.

Которое попало в словарь скриптовой машины и которое скриптовая машина опознаёт как валидный идентификатор. И который начинает участвовать в процессе компиляции скриптов.

Зачем это нужно?

Ну конечно не для того, чтобы "писать по-русски". Это конечно - утопия.

Идея в том, что подобные тесты можно "читать по-русски".

Тест приобретает вид TestCase'а.

И его может выполнять не только скриптовая машина, но и человек.

Ну и - "самодокументируемость кода".

Таким образом выводя "нужные ручки" со стороны Delphi (не обязательно через RTTI) и описывая "высокоуровневые" слова в промежуточных словарях тестовой машины - можно быстро "набрать массу", необходимую для написания тестов в виде TestCase'ов.

Немножко о том "как всё это устроено внутри" читайте тут - http://18delphi.blogspot.ru/2013/11/gui_5.html

7 комментариев:

  1. А как Вы можете классифицировать созданный язык для тестовой машины? :)
    Ну типа студентам объяснять терминологически грамотно, что Александр наваял.

    ОтветитьУдалить
  2. Правда с лямбдами. Но это для современных императивных языков - не новость.

    ОтветитьУдалить
  3. При создании этого скриптового языка мы изначально не преследовали цель попасть в какую-то классификацию согласно академическим традициям. Хотелось, чтобы язык развивался естественным образом, через практику применения простыми пользователями, которые и пишут кейсы. Можно сказать, что возможности языка здесь не рафинированные, а синтаксис выкристаллизовывался поэтапно.
    Сейчас можно, конечно, спекулировать на критике такого подхода, но придумать что-то более удобное и, главное, эффективное сложно. Если вы посмотрите на готовые "тест-машины", то в принципе они все похожи.

    Ну а так - это, да, императивный язык :) Но обозвать язык "императивным", не понимая, как он при этом реализуется - от этого не будет пользы студентам. Хотя сам по себе данный пример весьма показателен.

    ОтветитьУдалить
  4. Скажите, как у вас решается (планируете решать) проблема несоответствия терминологии языка тестов и языка предметной области? Например, тестеру нужно проверить как заполняется какой-нибудь справочник. Он вместо того, что бы написать в скрипте "заполнить справочник Х такими-то данными" пишет что-то типа "Открыть пункт меню такой-то, нажать на пункт меню такой-то и т.д.", получается, что тестеры должны знать архитектуру приложения. По опыту это довольно трудоемко, мы в свое время от такого тестирования отказались, руководство не поняло в чем смысл таких трудозатрат, поэтому сейчас тестируем по старинке ручками.

    ОтветитьУдалить
  5. Так о том и речь, что тестер должен оперировать ПОНЯТИЯМИ СИСТЕМЫ и ПРЕДМЕТНОЙ области. К этому и стремимся.

    Просто у нас "меню" - это "предметная область". В некотором роде.

    А вообще говоря - никто не мешает сделать слово "заполнить справочник {(Х)} такими-то {(данными)}"

    Нет никаких препятствие для реализации этого.

    Ну с учётом "ручек" со стороны Delphi и исходного приложения.

    Да! В приложение надо ВСТРАИВАТЬ код для скриптов. Аксиоматику. Под IfDef. Но как показывает практика - это НЕ МЕШАЕТ.

    Тот же TestComplete - тоже встраивает специальный "тестовый функционал".

    ОтветитьУдалить
  6. TestComplete - http://smartbear.com/products/qa-tools/automated-testing-tools/

    ОтветитьУдалить