Автоматизация тестирования для Delphi

© 2002 Войнов Николай

Вы когда-нибудь сталкивались с проблемами при тестировании ваших программ? Если нет – то Вы либо супер-профессионал, либо не писали больших программ, либо не цените Ваше время. Из многочисленных печатных источников по разработке программного обеспечения (ПО), говорится, что фаза тестирования занимает 40% трудозатрат по проекту создания ПО.

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

Об автоматизированном тестировании программ до этого я знал лишь понаслышке. Буквально следующее: "программа для тестирования InterBase в десятки раз превышает размер самого InterBase".

И был еще "Delphi 6 Campaign CD", приобретенный около полугода назад, на котором присутствовало средство для автоматизированного тестирования – DUnit, библиотека классов, предназначенная для поддержки тестирования программ, принятых в экстремальном программировании и пподдерживающая Delphi4 и выше.

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

DUnit предлагает классы, которые позволяют вам просто организовать и выполнить созданные вами тесты. Предлагаются две опции для запуска ваших тестов:

  • с графическим интерфейсом, когда вы можете выбрать индивидуальные тесты и группы тестов
  • в виде консольных приложений

DUnit была создана на основе библиотеки JUnit, разработанной Кеннетом Беком(Kent Beck) и Эрихом Гамма(Erich Gamma) для языка Java, которая превратилась в мощный инструмент, для программирования на Delphi. Оригинальный порт под Delphi был сделан Juanco Anez и в настоящее время поддерживается DUnit Group на SourceForge.

Кто заинтересовался, может идти сразу на домашнюю страницу DUnit и скачивать библиотеку: DUnit homepage at SourceForge.

Почитал Readme, посмотрел примеры – вдохновляет. Решено было сделать небольшой пилотный проект, уж очень хотелось опробовать автоматизированное тестирование и проверить несколько новых идей в реализации интерфейса.

В основном занимаюсь разработкой ПО, предназначенного для автоматизации складкой деятельности решено было сделать нечто такое, что пригодилось бы мне в повседневной деятельности. Для демонстрации тестирования с библиотекой DUnit была создана программа SalesMgr. Если вы читали Ксавье и Пачеко Delphi Developers Guide и дошли до главы 33 – то однозначно найдете много общего. Есть четыре формы Склад, Клиенты, Новая продажа и архив продаж. Использовались сервер FireBird( +FibPlus). Кроме того, хотелось попробовать контролы из DECOSP Lib.

Программа выполняет следующие действия

и упрощенно имеет следующую структуру

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

Посмотрим, чем нам может помочь DUnit. Все мы тестировать не будем, ограничимся основными операциями.

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

Итак, займемся последовательным тестированием модулей:

  1. TFibDataModule
    • Login
    • Logout
  2. TSalesDataModule
    • NewSale
    • SaveSale
  3. TfrmNewSale
    • EnabledOp
    • sbAcceptClick
    • sbCancelClick

Чем нам может помочь DUnit? В каталоге \Contrib\XPGen был обнаружен кодогенератор! Всего за пару минут можно сделать шаблон теста для модуля. Простая, но полезная утилитка, экономит массу времени.

Надеюсь, вы уже посмотрели Readme к DUnit, поэтому не будем отвлекаться на основных моментах.

Итак, запускаем XPGen и подаем ему на вход dmFib.pas(TFibDataModule), немного правим и получаем код для тестирования функций dmFib:

unit Test_dmFib;
interface
uses
    TestFramework, SysUtils, Forms, dmFib;

type
    CRACK_TFibDataModule = class(TFibDataModule);
    Check_TFibDataModule = class(TTestCase)
    private
        DM: TFibDataModule;
    public
        //это для иннициализации тестов
        procedure setUp; override;
        procedure tearDown; override;
    published
        //это непосредственно тесты
        procedure VerifyLogin;
        procedure VerifyLogout;
    end;

function Suite : ITestSuite;

implementation

function Suite : ITestSuite;
begin
    result := TTestSuite.Create('dmFib Tests');
    // result.addTest(Check_TFibDataModule); почему-то не работает, хотя сгенерировано автомат.
end;
procedure Check_TFibDataModule.setUp;
begin
    DM := TFibDataModule.Create(Application);
end;
procedure Check_TFibDataModule.tearDown;
begin
    DM.Free;
end;

procedure Check_TFibDataModule.VerifyLogin;
begin
    DM.Login;
    Check(DM.dbFib.Connected = True, 'Login failure!');
end;
procedure Check_TFibDataModule.VerifyLogout;
begin
    DM.Login; DM.Logout;
    Check(not DM.dbFib.Connected, 'Logout failure!');
end;
initialization
    TestFramework.RegisterTest('dmFib', Check_TFibDataModule.Suite);
end.

Код проекта:

program AutoTestPrj;
uses
    Forms,
    TestFrameWork,
    GUITestRunner,
    Test_dmFib in 'Test_dmFib.pas',
    dmFib in '..\CommonFiles\dmFib.pas' {FibDataModule: TDataModule},
    common in '..\commonfiles\common.pas',
    Test_dmSales in 'Test_dmSales.pas';
{$R *.res}
begin
    Application.Initialize;
    GUITestRunner.RunRegisteredTests;
end.

Запускаем и видим:

Нажимаем на всем знакомую зеленую треугольную кнопочку и получаем первый автоматизированный тест! Думаю, проблем пока не возникло? У меня это получилось после часа изучения демок к библиотеке (у меня был готовый шаблон проекта, поэтому получилось так быстро).

А для тестирования функций TsalesDataModule придется немного повозиться. Это связано с инициализацией входных данных.

unit Test_dmSales;

interface

uses
    TestFramework,
    SysUtils,
    Forms,
    dmSales;

type

CRACK_TSalesDataModule = class(TSalesDataModule);

Check_TSalesDataModule = class(TTestCase)
    public
        procedure setUp; override;
        procedure tearDown; override;
    published
        procedure VerifyNewSale;
end;

function Suite : ITestSuite;

implementation

uses dmAttr, dmFib;

function Suite : ITestSuite;
begin
    result := TTestSuite.Create('dmSales Tests');
    // result.addTest(testSuiteOf(Check_TSalesDataModule));
end;

procedure Check_TSalesDataModule.setUp;
var ctg: Integer; meas: string;
begin
    FibDataModule := TFibDataModule.Create(Application);
    AttrDataModule := TAttrDataModule.Create(Application);
    SalesDataModule := TSalesDataModule.Create(Application);
    FibDataModule.Login;

    { TODO 1 -cDUnit_TEST : добавить тестовые ед.изм. и категории }
    AttrDataModule.OpenAttrs;
    if AttrDataModule.dtSection.RecordCount = 0 then
        AttrDataModule.__InsCatg('Test_Catg');
    if AttrDataModule.dtMeasure.RecordCount = 0 then
        AttrDataModule.__InsMeas('TESTMEA');

    ctg := AttrDataModule.GetCatgId;
    meas:= AttrDataModule.GetMeasure;

    { TODO 1 -cDUnit_TEST : добавить тестовое наличие товара на складе }
    SalesDataModule.OpenPart;
    SalesDataModule.__InsPart(1,ctg,'Part Position 1',50,5,6,meas);
    SalesDataModule.__InsPart(1,ctg,'Part Position 2',50,5,6,meas);
    SalesDataModule.__InsPart(1,ctg,'Part Position 3',50,5,6,meas);
    SalesDataModule.__InsPart(1,ctg,'Part Position 4',50,5,6,meas);
    SalesDataModule.__InsPart(1,ctg,'Part Position 5',50,5,6,meas);

    { TODO 1 -cDUnit_TEST : добавить тестовых покупателей }
    meas := DateToStr(Now);
    SalesDataModule.OpenCust;
    SalesDataModule.__InsCust(1,'Customer 1 '+meas,0,'Test Customer 1');
    SalesDataModule.__InsCust(1,'Customer 2 '+meas,0,'Test Customer 2');

    FibDataModule.CommitRetainingAll;
end;

procedure Check_TSalesDataModule.tearDown;
begin
    FibDataModule.Logout;
    SalesDataModule.Free;
    AttrDataModule.Free;
    FibDataModule.Free;
end;

////////////////////////////////////////////////////////////////////////////////
procedure Check_TSalesDataModule.VerifyNewSale;
begin
    with SalesDataModule do begin
        NewSale; //добавляем по единице каждого товара
        Check(dtPrice.RecordCount <> 0, 'нечего продавать');
        if dtPrice.RecordCount = 0 then Exit;
        dtPrice.First;
        while not dtPrice.Eof do begin
            dtPrice.Edit;
            dtPrice.FBN('QTY').AsInteger := 1;
            dtPrice.Next;
        end;
        SaveNewSale(Now, DateToStr(Now) + ' Test_dmSales');
        CloseNewSale;
        //ищем нашу новую продажу
        OpenSales; dtSales.Last;
        Check(dtSales.FBN('DESCR').AsString = DateToStr(Now) + ' Test_dmSales','NewSale failure!');
    end;
end;

initialization
    TestFramework.RegisterTest('dmSales', Check_TSalesDataModule.Suite);
end.

И, наконец, протестируем интерфейс! Для этого пришлось покопаться в UnitTestGUITesting.pas, так как в примерах этого не было. Все как обычно – запускаем XPGen, даем ему на вход fmNewSale.pas, подправляем немного в соответствии нашими целями.

unit Test_fmNewSale;
interface
uses
    TestFramework,
    GUITesting,
    GUITestRunner,
    SysUtils,
    Graphics,
    Windows,
    Classes,
    Forms,
    fmNewSale;

type
    CRACK_TfrmNewSale = class(TfrmNewSale);
    Check_TfrmNewSale = class(TGUITestCase)
        private
             NewSaleFrm: TfrmNewSale;
        public
            procedure setUp; override;
            procedure tearDown; override;
        published
            procedure VerifyEnabledOp;
            procedure VerifysbAcceptClick;
            procedure VerifysbCancelClick;
end;

function Suite : ITestSuite;

implementation

uses common, dmAttr, dmFib, dmSales, fmFilter, FrActionFrm;

function Suite : ITestSuite;
begin
    result := TTestSuite.Create('fmNewSale Tests');
end;

procedure Check_TfrmNewSale.setUp;
begin
    inherited;
    FibDataModule := TFibDataModule.Create(nil);
    AttrDataModule := TAttrDataModule.Create(nil);
    SalesDataModule := TSalesDataModule.Create(nil);
    FibDataModule.Login;
    SalesDataModule.OpenCust;

    NewSaleFrm := TfrmNewSale.Create(nil);
    GUI := NewSaleFrm;
end;

procedure Check_TfrmNewSale.tearDown;
begin
    GUI := nil;
    NewSaleFrm.Free;
    inherited;
end;

procedure Check_TfrmNewSale.VerifyEnabledOp;
begin
    Show;
    Check(NewSaleFrm.sbAccept.Enabled = False, 'можно произвести пустую операцию ?');
    Check(NewSaleFrm.sbCancel.Enabled = False, 'можно отменить пустую операцию ?');
    SalesDataModule.dtPrice.Edit;
    SalesDataModule.dtPrice.FBN('QTY').AsInteger := 0;
    SalesDataModule.dtPrice.Post;
    Check(NewSaleFrm.sbAccept.Enabled = False, 'можно произвести пустую операцию ?');
    Check(NewSaleFrm.sbCancel.Enabled = False, 'можно отменить пустую операцию ?');
end;

procedure Check_TfrmNewSale.VerifysbAcceptClick;
begin
    Show;
    Check(NewSaleFrm.AddingOp = False, 'пустая операция ?');
    SalesDataModule.dtPrice.Edit;
    SalesDataModule.dtPrice.FBN('QTY').AsInteger :=1;
    SalesDataModule.dtPrice.Post;
    Check(NewSaleFrm.AddingOp, 'непустая операция ?');
    NewSaleFrm.sbAccept.Click;
    Check(NewSaleFrm.AddingOp = False, 'пустая операция ?');
end;

procedure Check_TfrmNewSale.VerifysbCancelClick;
begin
    Show;
    Check(NewSaleFrm.AddingOp = False, 'пустая операция ?');
    SalesDataModule.dtPrice.Edit;
    SalesDataModule.dtPrice.FBN('QTY').AsInteger :=1;
    SalesDataModule.dtPrice.Post;
    Check(NewSaleFrm.AddingOp, 'непустая операция ?');
    NewSaleFrm.sbCancel.Click;
    Check(NewSaleFrm.AddingOp = False, 'пустая операция ?');
end;

initialization
    TestFramework.RegisterTest('NewSale', Check_TfrmNewSale.Suite);
end.

Пришлось немного переделать свою форму-шаблон, так и не смог справиться со стандартными диалогами, которые создаются методом MessageDlg.

Окончательно проект принял такой вид:

program AutoTestPrj;
uses
    Forms,
    TestFrameWork,
    GUITestRunner,
    Test_dmFib in 'Test_dmFib.pas',
    dmFib in '..\CommonFiles\dmFib.pas' {FibDataModule: TDataModule},
    common in '..\commonfiles\common.pas',
    dmSales in '..\SalesMgr\dmSales.pas' {SalesDataModule: TDataModule},
    dmAttr in '..\commonfiles\dmAttr.pas' {AttrDataModule: TDataModule},
    fmFilter in '..\commonfiles\fmFilter.pas' {frmInvFilter},
    FrActionFrm in '..\commonfiles\FrActionFrm.pas' {FrActionForm},
    fmNewSale in '..\SalesMgr\fmNewSale.pas' {frmNewSale},
    OpTemplateFrm in '..\commonfiles\OpTemplateFrm.pas' {OpTemplateForm},
    ChildFrm in '..\commonfiles\ChildFrm.pas' {ChildForm},
    ItemsFm in '..\commonfiles\ItemsFm.pas' {ItemsFrame: TFrame},
    Test_dmSales in 'Test_dmSales.pas',
    Test_fmNewSale in 'Test_fmNewSale.pas';

{$R *.res}

begin
    Application.Initialize;
    GUITestRunner.RunRegisteredTests;
end.

Запускайте тест, смотрите. Если, кто продвинется до тестирования диалогов (клацания кнопок на диалогах) – поделитесь информацией.



Опубликовал admin
31 Июл, Суббота 2004г.
http://www.nominal.su/ рольставни объявления проспект онлайн калининград.


Программирование для чайников.