Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Прочее";
Текущий архив: 2009.11.01;
Скачать: [xml.tar.bz2];

Вниз

Как тестировать мето Assign?   Найти похожие ветки 

 
Kolan ©   (2009-09-02 13:38) [0]

Здравствуйте!

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

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

Понятно, что тест не должен изменяться при добавлении полей.


 
DrPass ©   (2009-09-02 13:57) [1]

Для published-полей можно, т.к. они вытягиваются из RTTI, и тест может сам перебрать сколько_их_там_наделано. Для остальных нет.


 
Kolan ©   (2009-09-02 14:35) [2]

А кто-нибудь тестировал подобные методы? Игорь?


 
jack128_   (2009-09-02 14:52) [3]

я так понимаю ассигн между объектами одного класса идет???  если они умеются стримится - то застримь их и сравни стримы побайтово.


 
Kolan ©   (2009-09-02 22:47) [4]

А как же ссылки, Джек?

Просто все соглашаются, что ручное сравнение полей — это хорошо, но как не забывать его дописывать?


 
Игорь Шевченко ©   (2009-09-03 00:15) [5]


> А кто-нибудь тестировал подобные методы? Игорь?


Я не вижу сложностей в тестировании метода assign, тем более, что при создании объекта его поля обнуляются. Присваивай при создании объекта для теста всем его полям заведомо ненулевые значения, после выполнения assign сравнивай то, что получилось с тем, что должно получиться.
Как не забывать дописывать - например, следить, чтобы адреса полей в тестовом сравнении были непрерывными и адрес последнего поля был на его размер меньше чем адрес начала объекта + InstanceSize


 
Kolan ©   (2009-09-03 01:33) [6]

А как понять, что поле последнее, если на момент теста его еще нет? Или делать все поля паблишт?


 
oxffff ©   (2009-09-03 09:24) [7]


> Игорь Шевченко ©   (03.09.09 00:15) [5]
>
> > А кто-нибудь тестировал подобные методы? Игорь?
>
>
> Я не вижу сложностей в тестировании метода assign, тем более,
>  что при создании объекта его поля обнуляются. Присваивай
> при создании объекта для теста всем его полям заведомо ненулевые
> значения, после выполнения assign сравнивай то, что получилось
> с тем, что должно получиться.
> Как не забывать дописывать - например, следить, чтобы адреса
> полей в тестовом сравнении были непрерывными и адрес последнего
> поля был на его размер меньше чем адрес начала объекта +
> InstanceSize


А как насчет скрытых полей?

1. указателей на VMT реализуемых интерфейсов
2. В новых версиях в конце объекта есть скрытое поле под TMonitor


 
Омлет   (2009-09-03 09:30) [8]

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


 
Игорь Шевченко ©   (2009-09-03 09:41) [9]

oxffff ©   (03.09.09 09:24) [7]

Скрытые поля находятся в начале объекта (по меньшей мере на тех версиях Delphi, с которыми я имел дело), так что я не вижу каких-либо затруднений.


 
oxffff ©   (2009-09-03 09:49) [10]


> Игорь Шевченко ©   (03.09.09 09:41) [9]


Это неправда. :)

1. см. функциональность TMonitor
2.  где будет поле указатель на VMT ISomeInterface
 AClass=Class(Tobject)
 a,b:integer;
 end;

 BClass=Class(AClass,ISomeInterface)

 end;


 
Игорь Шевченко ©   (2009-09-03 09:57) [11]


> 1. см. функциональность TMonitor


где смотреть ? У себя (D2006) не нашел


>  где будет поле указатель на VMT ISomeInterface


по отрицательным смещениям от начала объекта ?


 
oxffff ©   (2009-09-03 09:58) [12]


> А можно ли написать тест для метода Assign так, чтобы если
> добавил новое поле и забыл дописать присвоение в Assign,
>  то тест не срабатывал.


А компилятор по твоему может понять эквивалентность

A.Field1=B.Field1
A.Field2=B.Field2

и например

CopyMemory(@A.Field1,@B.Field1,Sizeof(A.Field1)+Sizeof(A.Field2))


 
oxffff ©   (2009-09-03 10:01) [13]


> Игорь Шевченко ©   (03.09.09 09:57) [11]
> по отрицательным смещениям от начала объекта ?


А вас на мысли ни на какие не наводит код

procedure TObject.FreeInstance;
begin
 CleanupInstance;
 _FreeMem(Self);
end;

Почему стоит имеено self, а не self-SizeofHiddenFields.


 
Игорь Шевченко ©   (2009-09-03 10:21) [14]

oxffff ©   (03.09.09 10:01) [13]

А тебя не наводит на мысль тот факт, что hiddenfields находятся в vmt, единой для всех экземпляров класса, а не у каждого экземпляра ?


 
oxffff ©   (2009-09-03 10:35) [15]


> Игорь Шевченко ©   (03.09.09 10:21) [14]


Во первых такого поля  как hiddenfields нет.

Есть например

 vmtSelfPtr           = -88;
 vmtIntfTable         = -84;
 vmtAutoTable         = -80;
 vmtInitTable         = -76;
 vmtTypeInfo          = -72;
 vmtFieldTable        = -68;
 vmtMethodTable       = -64;
 vmtDynamicTable      = -60;
 vmtClassName         = -56;
 vmtInstanceSize      = -52;
 vmtParent            = -48;

Далее черным по белому написано что при освобождении объекта  
вызывается деаллокатор c указателем на начало объекта. Ни на -8 как например в динамических массивах, и строках. А именно на начало объекта в привычном понимании.

И самое главное откройте отладчик и посмотрите что да как.


 
oxffff ©   (2009-09-03 10:53) [16]


> Игорь Шевченко ©   (03.09.09 10:21) [14]
> oxffff ©   (03.09.09 10:01) [13]
>
> А тебя не наводит на мысль тот факт, что hiddenfields находятся
> в vmt, единой для всех экземпляров класса, а не у каждого
> экземпляра ?


Если речь идет о том, что все скрытые поля о которых идет речь находятся в VMT, то это неправда. Поскольку интерфейс реализуется объектом, а не метаклассом.Также как и монитор связан с объектом а не метаклассом.


 
Игорь Шевченко ©   (2009-09-03 11:00) [17]

oxffff ©   (03.09.09 10:53) [16]


> Поскольку интерфейс реализуется объектом, а не метаклассом.


Открой отладчик и посмотри. По меньшей мере в D2006 реализация интерфейса одинакова для всех экземпляров класса, а ссылка на эту реализацию находится в vmt.
Что такое TMonitor, я не знаю, в 2006 не нашел


 
oxffff ©   (2009-09-03 11:08) [18]


> Игорь Шевченко ©   (03.09.09 11:00) [17]
> oxffff ©   (03.09.09 10:53) [16]
>
>
> > Поскольку интерфейс реализуется объектом, а не метаклассом.
>
>
>
> Открой отладчик и посмотри. По меньшей мере в D2006 реализация
> интерфейса одинакова для всех экземпляров класса, а ссылка
> на эту реализацию находится в vmt.



Неправда. см.

Aobject=class
//
end;

Bobject=class(Aobject,Iunknown)
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;

showmessage(inttostr(Aobject.InstanceSize));
showmessage(inttostr(Bobject.InstanceSize));


 
Игорь Шевченко ©   (2009-09-03 11:14) [19]

oxffff ©   (03.09.09 11:08) [18]

Да, все верно, указатель на экземпляр в таблицу интерфейсов прописывается.


 
oxffff ©   (2009-09-03 11:25) [20]


> Игорь Шевченко ©   (03.09.09 11:14) [19]


В какую такую таблицу интерфейсов прописывается?
Нет ничего такого.
Экземпляр содержит в качестве скрытого поля указатель на VMT.

А сам интерфейс является двойным указателем на VMT.

см.
http://blog.excastle.com/2008/05/10/interfaces-and-reference-equality-beware/


 
oxffff ©   (2009-09-03 11:27) [21]


> см.
> http://blog.excastle.com/2008/05/10/interfaces-and-reference-
> equality-beware/


О. Я ему еще и комментарии оставил. Ж)


 
Игорь Шевченко ©   (2009-09-03 11:34) [22]


> В какую такую таблицу интерфейсов прописывается?
> Нет ничего такого


Вот в такую:

class function TObject.InitInstance(Instance: Pointer): TObject;
{$IFDEF PUREPASCAL}
var
 IntfTable: PInterfaceTable;
 ClassPtr: TClass;
 I: Integer;
begin
 FillChar(Instance^, InstanceSize, 0);
 PInteger(Instance)^ := Integer(Self);
 ClassPtr := Self;
 while ClassPtr <> nil do
 begin
   IntfTable := ClassPtr.GetInterfaceTable;
   if IntfTable <> nil then
     for I := 0 to IntfTable.EntryCount-1 do
   with IntfTable.Entries[I] do
   begin
     if VTable <> nil then
       PInteger(@PChar(Instance)[IOffset])^ := Integer(VTable);
   end;
   ClassPtr := ClassPtr.ClassParent;
 end;
 Result := Instance;
end;
{$ELSE}


 
oxffff ©   (2009-09-03 11:52) [23]


> Игорь Шевченко ©   (03.09.09 11:34) [22]
>
> > В какую такую таблицу интерфейсов прописывается?
> > Нет ничего такого


Это не таблица, поскольку все записи по PInteger(@PChar(Instance)[IOffset])^ не обязательно будет составлять непрерывный участок памяти в экземпляре.

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


 
Cobalt ©   (2009-09-03 12:03) [24]

Насчет полей - это все, конечно, интересно. VMT, все дела...
НО.
если при Assign надо будет еще и какие-то действия производить над полями, то все-равно придется проверять лично.

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


 
Kolan ©   (2009-09-03 12:24) [25]

Cobalt, но как не забыть?


 
brother ©   (2009-09-03 12:27) [26]

> Cobalt, но как не забыть?

записывай на бумажке...


 
Kolan ©   (2009-09-03 15:24) [27]

Еще вопрос, кстати.

Большой ли грех не удалять объекты в тестах?


 
Kolan ©   (2009-09-04 18:27) [28]

Если честно, я так и не разобрался как сделать тест сабжа.

Приведу пример.

Есть кредит:

 TCredit = class(TSimpleIDObject)
 private
   FCreditPeriod: Integer;
   FCreditSize: Currency;
   FYearPercent: Integer;
   FPaymentDay: Integer;
   FContractNumber: string;
   FContractDate: TDateTime;
   FPaymentsSumm: Currency;
   FDelayCount: Integer;
   FActiveDelaysCount: Integer;
   FFullMustBePaymentSum: Currency;
 public
   procedure Assign(ACredit: TCredit);
   function IsPaiedOff: Boolean;
   function LeftPaymentDiffrence: Currency;

   property ContractNumber: string read FContractNumber write FContractNumber;
   property ContractDate: TDateTime read FContractDate write FContractDate;
   property PaymentDay: Integer read FPaymentDay write FPaymentDay;
   property CreditSize: Currency read FCreditSize write FCreditSize;
   property CreditPeriod: Integer read FCreditPeriod write FCreditPeriod;
   property YearPercent: Integer read FYearPercent write FYearPercent;
   property PaymentsSumm: Currency read FPaymentsSumm write FPaymentsSumm;
   property FullMustBePaymentSum: Currency read FFullMustBePaymentSum write FFullMustBePaymentSum;
   property DelayCount: Integer read FDelayCount write FDelayCount;
   property ActiveDelaysCount: Integer read FActiveDelaysCount write FActiveDelaysCount;
 end;


Есть метод Assign:

procedure TCredit.Assign(ACredit: TCredit);
begin
 inherited Assign(ACredit);
 if Assigned(ACredit) then
 begin
   FCreditPeriod := ACredit.CreditPeriod;
   FCreditSize := ACredit.CreditSize;
   FYearPercent := ACredit.YearPercent;
   FPaymentDay := ACredit.PaymentDay;
   FContractNumber := ACredit.ContractNumber;
   FContractDate := ACredit.ContractDate;
   FPaymentsSumm := ACredit.PaymentsSumm;
   FFullMustBePaymentSum := ACredit.FullMustBePaymentSum;
   FDelayCount := ACredit.DelayCount;
   FActiveDelaysCount := ACredit.ActiveDelaysCount;
 end;
end;


Есть тест:

procedure TestTCredit.TestAssign;
var
 ACredit: TCredit;
begin
 ACredit := TCredit.Create;
 try
   ACredit.ID := 1;

   FCredit.Assign(ACredit);

   Assert(FCredit.ID = 1);
 finally
   ACredit.Free;
 end;
end;


Как надо переделать тест, чтобы не забывать про добавление полей?


 
oxffff ©   (2009-09-04 19:01) [29]


> Kolan ©   (04.09.09 18:27) [28


1. Delphi 2010 + RTTI в руки.
2. Каждое поле делаешь отдельным объектом или вариантой записью и делаешь примерно так

Пример для варианта

TSomeValueStuff=record
name:string;
value:variant;
end;

TCompositeObject = class( ...)
ItemsContainer:TLIST<TSomeValueStuff>;
constructor create(Names:arrray of string);
procedure assign(source:TCompositeObject);
end;

TCredit =class(TCompositeObject)
constructor create();
end;

constructor TCompositeObject.create(Names:arrray of string);
var name:string;
    SomeValueStuff:TSomeValueStuff;
begin
ItemsContainer:= Tlist<...>.create;

for name in Names do
 begin
 SomeValueStuff.name=name;
 ItemsContainer.add(SomeValueStuff);
 end;
end;

constructor TCredit.create();
begin
inherited create("FCreditPeriod","FCreditSize",....);
end;

procedure assign(source:TCompositeObject);
var Value:TSomeValueStuff;
    Newvalue:TSomeValueStuff;
begin
if ClassType=source.classtype then
  begin
  for value in source.ItemsContainer do
  ItemsContainer[ItemsContainer.indexof(value)]:= value;
  end;
end;

+декструктор + ICompare для LIST<> сделаешь сам.


 
oxffff ©   (2009-09-04 19:02) [30]


> oxffff ©   (04.09.09 19:01) [29]


Это псевдокод.


 
Kolan ©   (2009-09-04 19:22) [31]

1. А для Д 2009?

А обращаться что по строке? Я хочу подсказок в ИДЕ и чтобы у других людей при виде этого не съехала крыша.


 
oxffff ©   (2009-09-04 19:25) [32]


> Kolan ©   (04.09.09 19:22) [31]
> 1. А для Д 2009?


Вариант 2


 
oxffff ©   (2009-09-04 19:27) [33]


> А обращаться что по строке? Я хочу подсказок в ИДЕ и чтобы
> у других людей при виде этого не съехала крыша.


Напиши дополнение к IDE через ToolAPi. :)


 
oxffff ©   (2009-09-04 19:29) [34]


> Kolan ©   (04.09.09 19:22) [31]
> 1. А для Д 2009?
>
> А обращаться что по строке? Я хочу подсказок в ИДЕ и


Может дописать алиасы к полям через.

property ContractNumber: string read FContractNumber write FContractNumber;
property ContractDate: TDateTime read FContractDate write FContractDate;
property PaymentDay: Integer read FPaymentDay write FPaymentDay;
property CreditSize: Currency read FCreditSize write FCreditSize

Однако assign у тебя будет один на любые композиции частей.


 
Игорь Шевченко ©   (2009-09-04 20:01) [35]

Kolan ©   (04.09.09 18:27) [28]

Как я делаю подобные тесты:

procedure TestTCredit.TestAssign;
var
ACredit: TCredit;
begin
ACredit := TCredit.Create;
try
  ACredit.Assign(FCredit);
  Assert(ACredit.IsEqual(FCredit));
finally
  ACredit.Free;
end;
end;

почему изменен порядок в assign - для единообразия с TPersistent


 
Kolan ©   (2009-09-04 22:11) [36]

Хороший тест, понятный. Только, вот...

А что в IsEqual? И как тестировать его?

--
Лично для меня методы IsEqual и IsEqual — почти одно и тоже.


 
Kolan ©   (2009-09-04 22:12) [37]

oxffff, это решения крутые, да, но я не пойду на такое усложнение ради теста. Проще и правда «не забывать».


 
Игорь Шевченко ©   (2009-09-04 22:16) [38]


>  И как тестировать его?


тем же тестом :) можно написать отдельный ради чистоты теории, но зачем ?


 
Kolan ©   (2009-09-04 22:25) [39]

Что-то я туплю.

Если я добавил поле, но забыл его присвоить (или сравнить) в IsEqual и Assign, то тест сработает, но ошибку не покажет.

Так? Тогда что проверяет этот тест? — Ничего полезного, имхо.


 
Игорь Шевченко ©   (2009-09-04 22:29) [40]

Kolan ©   (04.09.09 22:25) [39]

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



Страницы: 1 2 вся ветка

Форум: "Прочее";
Текущий архив: 2009.11.01;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.57 MB
Время: 0.007 c
2-1252426330
Alexei
2009-09-08 20:12
2009.11.01
Программное нажатие клавиши или сочетания клавиш


2-1252381073
TOR
2009-09-08 07:37
2009.11.01
Создается нерабочий компонент


2-1252616137
fics)
2009-09-11 00:55
2009.11.01
TQuery


2-1252953846
cyberspy85
2009-09-14 22:44
2009.11.01
не запускается скрипт


2-1252331505
noob_one
2009-09-07 17:51
2009.11.01
Кто-нибудь пользовался свойством Origin объекта Tfield?





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский