Блог человека, который 18-ть лет программирует на Delphi. И 25 лет программирует вообще. VCL, UML, MDA, тесты. Это не "учебник", это - "заметки на полях".
четверг, 26 февраля 2015 г.
Containers 3. Generics and without Generics...
Original in Russian: http://18delphi.blogspot.ru/2013/03/generic-generic.html
About containers. Table of contents
Akzhan Abdulin has already written it once... I am shaping... I have been using it for already 8 years...
Patterns in Delphi 7:
-------------------------------------------------------------------
Example:
------------------------------------------------------------------------------------------------
IN TOTAL:
----------------------------------------------------------------------------------------------
IntStack.pas:
----------------------------------------------------------------------------------------------
StringStack.pas:
!!!! It is clear that DYNAMIC ARRAY is used here as an example. It is clear that we could have introduced PItemType = ^_ItemType_ and could have operated with pointers and GetMem. But it would have complicated the example.
It is also clear that there is no control of boundary conditions.
Try it. May be, you will like it.
And about the true generic's you can read here - http://keeper89.blogspot.ru/2011/07/delphi.html
One of my realizations of this idea is described here - http://18delphi.blogspot.com/2013/07/2_18.html
About containers. Table of contents
Akzhan Abdulin has already written it once... I am shaping... I have been using it for already 8 years...
Patterns in Delphi 7:
TList = class(Parent)
Add(a: ItemType);
Insert(i: Integer; a: ItemType);
Sort();
end;
..
TList.impl
TList.Sort()
..
CompareItems(a, b) : Integer;
..
Parent = TObject;
ItemType = integer;
{$include TList.intf}
TIntList = class(TList)
..
CompareItems(a, b : Integer) : Integer;
begin
Result := a - b;
end;
{$Include TList.impl}
-------------------------------------------------------------------
Example:
------------------------------------------------------------------------------------------------
IN TOTAL:
stack.imp.pas:
{$IfNDef Stack_imp}
{$Define Stack_imp}
ItemsHolder = array of _ItemType_;
_Stack_ = {mixin} class(TObject)
private
// private fields
f_Items : ItemsHolder;
public
// public methods
procedure Push(const anItem: _ItemType_);
function Pop: _ItemType_;
end;//_Stack_
{$Else Stack_imp}
// start class _Stack_
procedure _Stack_.Push(const anItem: _ItemType_);
var
l_L : Integer;
begin
l_L := Length(f_Items);
SetLength(f_Items, l_L + 1);
f_Items[l_L] := anItem;
end;//_Stack_.Push
function _Stack_.Pop: _ItemType_;
var
l_L : Integer;
begin
l_L := Length(f_Items) - 1;
Result := f_Items[l_L];
SetLength(f_Items, l_L);
end;//_Stack_.Pop
{$EndIf Stack_imp}
----------------------------------------------------------------------------------------------
IntStack.pas:
unit IntStack;
interface
type
_ItemType_ = Integer;
{$Include Stack.imp.pas}
TIntStack = class(_Stack_)
end;//TIntStack
implementation
{$Include Stack.imp.pas}
end.
----------------------------------------------------------------------------------------------
StringStack.pas:
unit StringStack;
interface
type
_ItemType_ = AnsiString;
{$Include Stack.imp.pas}
TStringStack = class(_Stack_)
end;//TStringStack
implementation
{$Include Stack.imp.pas}
end.
!!!! It is clear that DYNAMIC ARRAY is used here as an example. It is clear that we could have introduced PItemType = ^_ItemType_ and could have operated with pointers and GetMem. But it would have complicated the example.
It is also clear that there is no control of boundary conditions.
Try it. May be, you will like it.
And about the true generic's you can read here - http://keeper89.blogspot.ru/2011/07/delphi.html
One of my realizations of this idea is described here - http://18delphi.blogspot.com/2013/07/2_18.html
About containers. Table of contents
Containers 1. Implementation of reference counting
Containers 2. My own implementation of IUnknown and reference counting. And mixins
Containers 3. Generics and without Generics...
Containers 4. Encapsulation of the work with the memory. Part 0
Containers 5. Encapsulation of the work with the memory. Part 1
Containers 6. Abstract containers
Containers 7. Abstract containers. Part 2
Containers 8. Deriving of specific atomic containers from abstract ones
Containers 9. Special containers. Part 2
Containers 10. About patterns and mixins
Containers 11. A little about using DUnit
Containers 12. DUnit patterns and tests
to be continued...
Containers 2. My own implementation of IUnknown and reference counting. And mixins
Containers 3. Generics and without Generics...
Containers 4. Encapsulation of the work with the memory. Part 0
Containers 5. Encapsulation of the work with the memory. Part 1
Containers 6. Abstract containers
Containers 7. Abstract containers. Part 2
Containers 8. Deriving of specific atomic containers from abstract ones
Containers 9. Special containers. Part 2
Containers 10. About patterns and mixins
Containers 11. A little about using DUnit
Containers 12. DUnit patterns and tests
to be continued...
Containers 2. My own implementation of IUnknown and reference counting. And mixins
Original in Russian: http://18delphi.blogspot.ru/2013/04/iunknown.html
About containers. Table of contents
Borland (now - Embarcadero), naturally, has implementation of IUnknown. It is called TInterfacedObject.
And it is not bad.
But I personally am not satisfied with its dissymmetry. If one has created the interface, he should work through the interface and, I pray, forget about the object. Put it in container as interface and pass on as interface. Compared to the object, the interface is extra expenses – for the extra indirect character of the calls of methods (something like virtual) and for the extra AddRef/Release while assigning to local variables. The more so because of the fact that the public methods of the object are different from those of the interface. That is as it should be. The public character of methods can depend on the authority of the user of the object (that is, by the way, a subject of a separate post, which, besides, gunsmoker has approached in some way here: http://www.gunsmoker.ru/2013/02/delphi-friendliness.htm
I hope I understood him correctly and did not interpret his thoughts the way I liked).
I am not happy with it in my daily tasks. Moreover, historically, my implementation of reference counting dates back to Delphi 1, even BEFORE the concept of interfaces has been introduced. And the interfaces I emulated using hack VMT and message (as far as I can tell, gunsmoker also writes about the similar technique).
The history of how my own reference counting has appeared is worth of special post. Meanwhile, briefly – editor and Undo/Redo. Where objects do not have the centralized "dad" (Owner/Parent). All objects equally can contain a reference to other objects. It is guaranteed that the objects they refer to are “alive” as long as they contain the reference correctly.
Of course, the true holy war could be raised on “do as they say” and “wise men do not think up with no purpose”. But I would like to address this issue in a separate post, and here I’ll tell about my implementation and at the same time demonstrate one more time “micro”-UML and the technique of using “mixins”.
The previous series were here:
http://18delphi.blogspot.com/2015/02/containers-1-implementation-of.html
Basing on this knowledge, let’s construct an object that implements IUnknown.
Let’s draw a pair of diagrams:
-- this is a “mixin” implementation of IUnknown.
And the specific implementation (inherited from TObject) is as follows:
The code for this all is like this:
The diagram of the tests of this all:
And the code of the test:
I hope the “symmetry” of reference counting is clear.
Let’s move on.
We’ll add an object that implements the “real” interface.
Let’s draw a pair of diagrams:
-- the classes diagram.
-- the diagram of implementation.
And the code:
And the test for this all:
And the code:
That is all about it...
The next series will be devoted to the implementation of various containers.
P.S. ??? It is obvious that I can write:
-- and get TmyInterfacedPersistentObject inherited from TPersistent ???
There are certain problems with such an inheritance from TComponent or TControl due to RegisterClass. Later – I will describe them. But if RegisterClass is not called for such classes, then EVERYTHING IS OK. This technique can be used. If it is called, it means I’ve “hacked” VCL in some way. Later – I will tell how. I just don’t understand – why uniqueness of the names in RegisterClass should be controlled. I have disabled the control and all works. For already 10 years.
Oh! For TComponent (and its descendants, particularly – Tcontrol) implicit instantiation of mixin” should be applied, because it - ALREADY - has QueryInterface and there’s no need to redefine it (and it is harmful). Later I’ll tell how. In a nutshell – of course, using IfDef.
Actually, “partial admixing” is fun :-) That is when mixin is not fully admixed and just a part of it, that the right IfDef has got.
------------------------------------
This symmetry of reference counting eliminates the problem described here: http://www.gunsmoker.ru/2013/04/plugins-9.html
("Mixing of manual and automatic management of lifetime " and "Double interfaces release").
About containers. Table of contents
Borland (now - Embarcadero), naturally, has implementation of IUnknown. It is called TInterfacedObject.
And it is not bad.
But I personally am not satisfied with its dissymmetry. If one has created the interface, he should work through the interface and, I pray, forget about the object. Put it in container as interface and pass on as interface. Compared to the object, the interface is extra expenses – for the extra indirect character of the calls of methods (something like virtual) and for the extra AddRef/Release while assigning to local variables. The more so because of the fact that the public methods of the object are different from those of the interface. That is as it should be. The public character of methods can depend on the authority of the user of the object (that is, by the way, a subject of a separate post, which, besides, gunsmoker has approached in some way here: http://www.gunsmoker.ru/2013/02/delphi-friendliness.htm
I hope I understood him correctly and did not interpret his thoughts the way I liked).
I am not happy with it in my daily tasks. Moreover, historically, my implementation of reference counting dates back to Delphi 1, even BEFORE the concept of interfaces has been introduced. And the interfaces I emulated using hack VMT and message (as far as I can tell, gunsmoker also writes about the similar technique).
The history of how my own reference counting has appeared is worth of special post. Meanwhile, briefly – editor and Undo/Redo. Where objects do not have the centralized "dad" (Owner/Parent). All objects equally can contain a reference to other objects. It is guaranteed that the objects they refer to are “alive” as long as they contain the reference correctly.
Of course, the true holy war could be raised on “do as they say” and “wise men do not think up with no purpose”. But I would like to address this issue in a separate post, and here I’ll tell about my implementation and at the same time demonstrate one more time “micro”-UML and the technique of using “mixins”.
The previous series were here:
http://18delphi.blogspot.com/2015/02/containers-1-implementation-of.html
Basing on this knowledge, let’s construct an object that implements IUnknown.
Let’s draw a pair of diagrams:
-- this is a “mixin” implementation of IUnknown.
And the specific implementation (inherited from TObject) is as follows:
The code for this all is like this:
RefCountedPrim.imp.pas:
{$IfNDef RefCountedPrim_imp}
//
// The library "L3$Basic Concepts"
// Generated from UML model, root element: <<impurity::class>> Shared Delphi Requirements for low-level libraries::L3$Basic Concepts::Ref Counting::RefCountedPrim
//
{$Define RefCountedPrim_imp}
_RefCountedPrim_ = {mixin} class(_RefCountedPrim_Parent_)
private
// private fields
f_RefCount : Integer;
{* Field for the property RefCount}
protected
// overridden protected methods
procedure FreeInstance; override;
public
// overridden public methods
destructor Destroy; override;
class function NewInstance: TObject; override;
protected
// protected methods
procedure Cleanup; virtual;
{* the function of object fields cleaning. }
public
// public methods
function Use: Pointer;
{* increment the reference counter by 1 and return the pointer to itself. }
function SetRefTo(var F): Boolean;
public
// public properties
property RefCount: Integer
read f_RefCount;
end;//_RefCountedPrim_
{$Else RefCountedPrim_imp}
// start class _RefCountedPrim_
procedure _RefCountedPrim_.Cleanup;
begin
end;//_RefCountedPrim_.Cleanup
function _RefCountedPrim_.Use: Pointer;
begin
if (Self <> nil) then
InterlockedIncrement(f_RefCount);
Result := Self;
end;//_RefCountedPrim_.Use
function _RefCountedPrim_.SetRefTo(var F): Boolean;
begin
if (Pointer(F) = Self) then
Result := false
else
begin
Result := true;
TObject(F).Free;
Pointer(F) := Self.Use;
end;//Pointer(F) = V
end;//_RefCountedPrim_.SetRefTo
destructor _RefCountedPrim_.Destroy;
begin
if (InterlockedDecrement(f_RefCount) = 0) then
begin
Inc(f_RefCount);
try
try
Cleanup;
finally
inherited Destroy;
end;//try..finally
finally
Dec(f_RefCount);
end;{try..finally}
end;//InterlockedDecrement(f_RefCount) = 0
end;//_RefCountedPrim_.Destroy
class function _RefCountedPrim_.NewInstance: TObject;
begin
Result := inherited NewInstance;
_RefCounted_(Result).Use;
end;//_RefCountedPrim_.NewInstance
procedure _RefCountedPrim_.FreeInstance;
begin
if (f_RefCount = 0) then
inherited FreeInstance;
end;//_RefCountedPrim_.FreeInstance
{$EndIf RefCountedPrim_imp}
-------------------------------
RefCounted.imp.pas:
{$IfNDef RefCounted_imp}
// The library "L3$Basic Concepts"
// Generated from UML model, root element: <<impurity::class>> Shared Delphi Requirements for low-level libraries::L3$Basic Concepts::Ref Counting::RefCounted
//
// In this class we collect methods which we are not allowed to override or call directly
//
{$Define RefCounted_imp}
_RefCountedPrim_Parent_ = _RefCounted_Parent_;
{$Include RefCountedPrim.imp.pas}
_RefCounted_ = {mixin} class(_RefCountedPrim_)
{* In this class we collect methods which we are not allowed to override or call directly }
public
// public methods
destructor Destroy;
{* To prevent being lured in overriding destroy. }
class function NewInstance: TObject;
procedure FreeInstance;
procedure AfterConstruction;
procedure BeforeDestruction;
end;//_RefCounted_
{$Else RefCounted_imp}
{$Include RefCountedPrim.imp.pas}
// start class _RefCounted_
destructor _RefCounted_.Destroy;
begin
assert(false, 'We are not supposed to get here');
inherited;
end;//_RefCounted_.Destroy
class function _RefCounted_.NewInstance: TObject;
begin
Result := nil;
assert(false);
end;//_RefCounted_.NewInstance
procedure _RefCounted_.FreeInstance;
begin
assert(false);
end;//_RefCounted_.FreeInstance
procedure _RefCounted_.AfterConstruction;
begin
assert(false);
end;//_RefCounted_.AfterConstruction
procedure _RefCounted_.BeforeDestruction;
begin
assert(false);
end;//_RefCounted_.BeforeDestruction
{$EndIf RefCounted_imp}
-------------------------------------------------
UnknownImpl.imp.pas:
{$IfNDef UnknownImpl_imp}
// The library "L3$Basic Concepts"
// Generated from UML model, root element: <<impurity::class>> Shared Delphi Requirements for the low-level libraries::L3$Basic Concepts::Ref Counting::UnknownImpl
//
{$Define UnknownImpl_imp}
_RefCounted_Parent_ = _UnknownImpl_Parent_;
{$Include RefCounted.imp.pas}
_UnknownImpl_ = {mixin} class(_RefCounted_)
public
// realized methods
function _AddRef: Integer; stdcall;
{* Increments reference counter. }
function _Release: Integer; stdcall;
{* Decrements reference counter. }
function QueryInterface(const IID: TGUID;
out Obj): HResult; stdcall;
{* Brings base interface to the required, if it is possible. }
end;//_UnknownImpl_
{$Else UnknownImpl_imp}
{$Include RefCounted.imp.pas}
// start class _UnknownImpl_
function _UnknownImpl_._AddRef: Integer;
begin
Use;
Result := RefCount;
// - here we’ve got problems with multithreading
end;//_UnknownImpl_._AddRef
function _UnknownImpl_._Release: Integer;
var
l_RC : Integer;
begin
l_RC := RefCount - 1;
Free;
Result := l_RC;
// - here we’ve got problems with multithreading
end;//_UnknownImpl_._Release
function _UnknownImpl_.QueryInterface(const IID: TGUID;
out Obj): HResult;
begin
if TObject(Self).GetInterface(IID, Obj) then
Result := S_Ok
else
Result := E_NoInterface;
end;//_UnknownImpl_.QueryInterface
{$EndIf UnknownImpl_imp}
---------------------------------
Unknown.imp.pas:
{$IfNDef Unknown_imp}
// The library "L3$Basic Concepts"
// Generated from UML model, root element: <<impurity::class>> Shared Delphi Requirements for the low-level libraries::L3$Basic Concepts::Ref Counting::Unknown
//
{$Define Unknown_imp}
_UnknownImpl_Parent_ = _Unknown_Parent_;
{$Include UnknownImpl.imp.pas}
_Unknown_ = {mixin} class(_UnknownImpl_, IUnknown)
end;//_Unknown_
{$Else Unknown_imp}
{$Include UnknownImpl.imp.pas}
{$EndIf Unknown_imp}
---------------------------------
myInterfacedObject.pas:
unit myInterfacedObject;
// The library "SandBox"
Interfaces::TmyInterfacedObject
//
interface
type
_Unknown_Parent_ = TObject;
{$Include Unknown.imp.pas}
TmyInterfacedObject = class(_Unknown_)
end;//TmyInterfacedObject
implementation
uses
Windows
;
{$Include Unknown.imp.pas}
end.
The diagram of the tests of this all:
And the code of the test:
unit myInterfacedObjectTest; // The library "SandBoxTest" // Generated from UML model, root element: <<testcase::class>> Shared Delphi Sand Box::SandBoxTest::Core::TmyInterfacedObjectTest // // interface uses TestFrameWork ; type TmyInterfacedObjectTest = class(TTestCase) published // published methods procedure DoIt; end;//TmyInterfacedObjectTest implementation uses myInterfacedObject, SysUtils ; // start class TmyInterfacedObjectTest procedure TmyInterfacedObjectTest.DoIt; var l_O : TmyInterfacedObject; l_AnotherRef : TmyInterfacedObject; l_A : IUnknown; l_B : IUnknown; begin l_AnotherRef := nil; try l_O := TmyInterfacedObject.Create; try Check(l_O.RefCount = 1); l_A := l_O; Check(l_O.RefCount = 2); l_A := nil; Check(l_O.RefCount = 1); l_AnotherRef := l_O.Use; Check(l_O.RefCount = 2); l_B := l_O; Check(l_O.RefCount = 3); finally FreeAndNil(l_O); end;//try..finally Check(l_AnotherRef.RefCount = 2); l_B := nil; Check(l_AnotherRef.RefCount = 1); finally FreeAndNil(l_AnotherRef); end;//try..finally end;//TmyInterfacedObjectTest.DoIt initialization TestFramework.RegisterTest(TmyInterfacedObjectTest.Suite); end.
I hope the “symmetry” of reference counting is clear.
Let’s move on.
We’ll add an object that implements the “real” interface.
Let’s draw a pair of diagrams:
-- the classes diagram.
-- the diagram of implementation.
And the code:
myReferenceCountGuard.pas:
unit myReferenceCountGuard;
// The library "SandBox"
// Generated from UML model, root element: <<simpleclass::class>> Shared Delphi Sand Box::SandBox::Basic Interfaces::TmyReferenceCountGuard
//
// Class for examples only
//
interface
uses
myInterfacedObject
;
type
ImyReferenceCountGuard = interface(IUnknown)
{* Interface for examples only }
['{84AAAF31-F3AC-4BBC-A1B7-4E338748921F}']
function GetRefCount: Integer;
end;//ImyReferenceCountGuard
TmyReferenceCountGuard = class(TmyInterfacedObject, ImyReferenceCountGuard)
{* Class for examples only }
protected
// realized methods
function GetRefCount: Integer;
public
// public methods
class function Make: ImyReferenceCountGuard; reintroduce;
{* Factory TmyReferenceCountGuard.Make }
end;//TmyReferenceCountGuard
implementation
// start class TmyReferenceCountGuard
class function TmyReferenceCountGuard.Make: ImyReferenceCountGuard;
var
l_Inst : TmyReferenceCountGuard;
begin
l_Inst := Create;
try
Result := l_Inst;
finally
l_Inst.Free;
end;//try..finally
end;
function TmyReferenceCountGuard.GetRefCount: Integer;
begin
Result := RefCount;
end;//TmyReferenceCountGuard.GetRefCount
end.
And the test for this all:
And the code:
myReferenceCountGuardTest.pas: unit myReferenceCountGuardTest; // The library "SandBoxTest" // Generated from UML model, root element: <<testcase::class>> Shared Delphi Sand Box::SandBoxTest::Core::TmyReferenceCountGuardTest // interface uses TestFrameWork ; type TmyReferenceCountGuardTest = class(TTestCase) published // published methods procedure DoIt; procedure CheckWithClause; end;//TmyReferenceCountGuardTest implementation uses SysUtils, myReferenceCountGuard ; // start class TmyReferenceCountGuardTest procedure TmyReferenceCountGuardTest.DoIt; var l_G : ImyReferenceCountGuard; l_Another : ImyReferenceCountGuard; begin l_G := TmyReferenceCountGuard.Make; Check(l_G.GetRefCount = 1); l_Another := l_G; Check(l_G.GetRefCount = 2); l_G := nil; Check(l_Another.GetRefCount = 1); l_Another := nil; end;//TmyReferenceCountGuardTest.DoIt procedure TmyReferenceCountGuardTest.CheckWithClause; var l_G : ImyReferenceCountGuard; begin // - here I wanted to show that inside the operator with the compiler increments the reference counter on interface/object “in parasite manner”, but I did not succeed in it: it turns out that in trivial cases it does not do it; when I find a non-trivial case I’ll show l_G := TmyReferenceCountGuard.Make; Check(l_G.GetRefCount = 1); with l_G do begin Check(GetRefCount = 1); Check(GetRefCount = 1); end;//with l_G Check(l_G.GetRefCount = 1); end;//TmyReferenceCountGuardTest.CheckWithClause initialization TestFramework.RegisterTest(TmyReferenceCountGuardTest.Suite); end.
That is all about it...
The next series will be devoted to the implementation of various containers.
P.S. ??? It is obvious that I can write:
---------------------------------
myInterfacedPersistentObject.pas:
unit myInterfacedPersistentObject;
// The library "SandBox"
// Generated from UML model, root element: <<simpleclass::class>> Shared Delphi Sand Box::SandBox::Basic Interfaces::TmyInterfacedPersistentObject
//
interface
type
_Unknown_Parent_ = TPersistent;
{$Include Unknown.imp.pas}
TmyInterfacedPersistentObject = class(_Unknown_)
end;//TmyInterfacedPersistentObject
implementation
uses
Windows
;
{$Include Unknown.imp.pas}
end.
-- and get TmyInterfacedPersistentObject inherited from TPersistent ???
There are certain problems with such an inheritance from TComponent or TControl due to RegisterClass. Later – I will describe them. But if RegisterClass is not called for such classes, then EVERYTHING IS OK. This technique can be used. If it is called, it means I’ve “hacked” VCL in some way. Later – I will tell how. I just don’t understand – why uniqueness of the names in RegisterClass should be controlled. I have disabled the control and all works. For already 10 years.
Oh! For TComponent (and its descendants, particularly – Tcontrol) implicit instantiation of mixin” should be applied, because it - ALREADY - has QueryInterface and there’s no need to redefine it (and it is harmful). Later I’ll tell how. In a nutshell – of course, using IfDef.
Actually, “partial admixing” is fun :-) That is when mixin is not fully admixed and just a part of it, that the right IfDef has got.
------------------------------------
This symmetry of reference counting eliminates the problem described here: http://www.gunsmoker.ru/2013/04/plugins-9.html
("Mixing of manual and automatic management of lifetime " and "Double interfaces release").
среда, 25 февраля 2015 г.
Containers 1. Implementation of reference counting
Original in Russian: http://18delphi.blogspot.ru/2013/03/blog-post_4606.html
About containers. Table of contents
For already 15 years I’ve been thinking why Borland did not implement reference counting in base classes but created the confusing class - TInterfacedObject.
Well, I implemented. Long ago. And happily use it.
One should CLEARLY separate “reference counting” from IUnknown. The first one is a CONCEPT, the second one is ONE OF the IMPLEMENTATIONS of this concept.
I’ve organized reference counting so that you can’t write:
You can only write:
Many do not like it. But I do like. I like symmetry. Who owns the object, releases it.
In fact, there are “go-carts” for those who like “short form” and for the “fighters” for the number of lines. They have been taken from Objective-C:
Next, I’d like to tell about the implementation.
First, you should understand one simple thing: there is compiler-magic when classes are created.
From the code:
and from the code:
the following is compiled:
I.e. code for constructor is nothing but a code for initialization, and code for destructor is a code for deinitialization.
The memory for the object is allocated in NewInstance and freed in DestroyInstance. This could be found in the documentation, but who really reads it :-)
The diagrams:
The code:
And the test:
... to be continued ...
About containers. Table of contents
For already 15 years I’ve been thinking why Borland did not implement reference counting in base classes but created the confusing class - TInterfacedObject.
Well, I implemented. Long ago. And happily use it.
One should CLEARLY separate “reference counting” from IUnknown. The first one is a CONCEPT, the second one is ONE OF the IMPLEMENTATIONS of this concept.
I’ve organized reference counting so that you can’t write:
List.Add(TItem.Create);
You can only write:
Item := TItem.Create; // the number of references is 1 try List.Add(Item); // the number of references is 2 finally FreeAndNil(Item); // the number of references is 1 end;
Many do not like it. But I do like. I like symmetry. Who owns the object, releases it.
In fact, there are “go-carts” for those who like “short form” and for the “fighters” for the number of lines. They have been taken from Objective-C:
List.Add(TItem.Create.Autorelease);
Next, I’d like to tell about the implementation.
First, you should understand one simple thing: there is compiler-magic when classes are created.
From the code:
A := TMyClass.Create;something like this is compiled:
A := TMyClass.NewInstance; A.Create;
and from the code:
A.Destroy;
the following is compiled:
A.Destroy; A.DestroyInstance
I.e. code for constructor is nothing but a code for initialization, and code for destructor is a code for deinitialization.
The memory for the object is allocated in NewInstance and freed in DestroyInstance. This could be found in the documentation, but who really reads it :-)
The diagrams:
The code:
RefCountedPrim.imp.pas:
{$IfNDef RefCountedPrim_imp}
{$Define RefCountedPrim_imp}
_RefCountedPrim_ = {mixin} class(_RefCountedPrim_Parent_)
private
// private fields
f_RefCount : Integer;
{* The field for the properties of RefCount}
protected
// overridden protected methods
procedure FreeInstance; override;
public
// overridden public methods
destructor Destroy; override;
class function NewInstance: TObject; override;
protected
// protected methods
procedure Cleanup; virtual;
{* The function of object fields cleaning. }
public
// public methods
function Use: Pointer;
{* increment the reference counter by 1 and return the pointer to itself. }
function SetRefTo(var F): Boolean;
public
// public properties
property RefCount: Integer
read f_RefCount;
end;//_RefCountedPrim_
{$Else RefCountedPrim_imp}
// start class _RefCountedPrim_
procedure _RefCountedPrim_.Cleanup;
begin
end;//_RefCountedPrim_.Cleanup
function _RefCountedPrim_.Use: Pointer;
begin
if (Self <> nil) then
InterlockedIncrement(f_RefCount);
Result := Self;
end;//_RefCountedPrim_.Use
function _RefCountedPrim_.SetRefTo(var F): Boolean;
begin
if (Pointer(F) = Self) then
Result := false
else
begin
Result := true;
TObject(F).Free;
Pointer(F) := Self.Use;
end;//Pointer(F) = V
end;//_RefCountedPrim_.SetRefTo
destructor _RefCountedPrim_.Destroy;
begin
if (InterlockedDecrement(f_RefCount) = 0) then
begin
Inc(f_RefCount);
try
try
Cleanup;
finally
inherited Destroy;
end;//try..finally
finally
Dec(f_RefCount);
end;{try..finally}
end;//InterlockedDecrement(f_RefCount) = 0
end;//_RefCountedPrim_.Destroy
class function _RefCountedPrim_.NewInstance: TObject;
begin
Result := inherited NewInstance;
_RefCounted_(Result).Use;
end;//_RefCountedPrim_.NewInstance
procedure _RefCountedPrim_.FreeInstance;
begin
if (f_RefCount = 0) then
inherited FreeInstance;
end;//_RefCountedPrim_.FreeInstance
{$EndIf RefCountedPrim_imp}
--------------------------------
RefCounted.imp.pas:
{$IfNDef RefCounted_imp}
{$Define RefCounted_imp}
_RefCountedPrim_Parent_ = _RefCounted_Parent_;
{$Include RefCountedPrim.imp.pas}
_RefCounted_ = {mixin} class(_RefCountedPrim_)
protected
// protected methods
destructor Destroy;
{* To prevent being lured in overriding destroy. }
end;//_RefCounted_
{$Else RefCounted_imp}
type _RefCountedPrim_R_ = _RefCounted_;
{$Include RefCountedPrim.imp.pas}
// start class _RefCounted_
destructor _RefCounted_.Destroy;
begin
assert(false, 'We are not supposed to get here');
inherited;
end;//_RefCounted_.Destroy
{$EndIf RefCounted_imp}
-------------------------------
Refcounted.pas:
unit Refcounted;
interface
type
_RefCounted_Parent_ = TObject;
{$Include RefCounted.imp.pas}
TRefcounted = class(_RefCounted_)
end;//TRefcounted
implementation
uses
Windows
;
{$Include RefCounted.imp.pas}
end.
And the test:
RefcountedTest.pas: unit RefcountedTest; interface uses BaseTest ; type TRefcountedTest = class(TBaseTest) published // published methods procedure DoIt; end;//TRefcountedTest implementation uses Refcounted, SysUtils, TestFrameWork ; // start class TRefcountedTest procedure TRefcountedTest.DoIt; var l_A : TRefcounted; l_B : TRefcounted; begin l_A := TRefcounted.Create; try Check(l_A.RefCount = 1); l_B := l_A.Use; try Check(l_A.RefCount = 2); Check(l_B.RefCount = 2); finally FreeAndNil(l_B); end;//try..finally Check(l_A.RefCount = 1); finally FreeAndNil(l_A); end;//try..finally end;//TRefcountedTest.DoIt initialization TestFramework.RegisterTest(TRefcountedTest.Suite); end.
... to be continued ...
About RS, time pressure and forgetting
Original in Russian: http://18delphi.blogspot.ru/2013/04/blog-post.html
To forget is human. Everyone forgets. That’s axiomatic.
Managers, designers, analysts, customers (stakeholders, to be brief) – sometime do not answer ALL questions put to them. That’s an assumption. You’re lucky if it is not your case Then, this post is not for you. Do not read it. Sleep in peace.
But! It often happens that when RS comes to developers, it raises questions.
Do your job honest.
There is nothing worse than the unclarified questions hidden deep in your code of genius. All the same no one will appreciate its value.
Have respect for your colleagues coming in about “ten years after you”.
ASK QUESTIONS!
And STRIVE to get answers.
If during the implementation of RS you have a question that calls up stakeholders to answer, then, in order to maintain the “stream” state and to keep up the pace of the developing, I recommend you the following:
1. Ask the question.
2. MAKE SURE it reaches addressee.
3. Write an ASSERT. Yes, right! The trivial assert with a condition you don’t understand.
4. Formulate the HEART of the question in the commentaries to the code.
5. Name the DATE and time of the question. On CVS it is tedious and we are too lazy to do it.
6. Name the number of “ticket” in data base or give a reference to the discussion if it takes place electronically.
7. Prepare rather meaningful message to log for user version of the system. DISABLE ASSERTs in user version of the system.
8. Write - EXIT. In case further you have written another code of genius.
9. Wait for the answer and continue to implement RS.
10. If after a “reasonable” period of time you don’t get the answer – update your commentary to the code with details about “when, who and where”.
11. If you do not get answers – close the task and delegate it to Quality Control Group. It is better to give the list of unclarified details. May be the QCG will help you and roll back the task giving the details.
12. If you get the answer, say “thank you” to the one that gives it. He has “done part of your work”.
13. Delete the ASSERT.
14. Write a sensible code taking the answer into account.
15. Write a test. This aspect of the system has taken time and nerves of lots of people. It already WORTH being covered with test.
16. Sleep in peace till some ASSERT wakes you up.
Try it. May be you will like it.
I warn you that stakeholder may not like it. Anyway, I’d try.
After all – they do their job well. Honest. THINK of those who will come to replace you. They may be not that genius as you are.
And if you think – “oh, I have 800 unfinished tasks, oh, this is too hard for me” – look here:
That’s just an self-irony, you know…
P.S. There are alternatives. But, as for me, they are not that effective.
That’s how I see them:
1. Do not ask questions and rely on your resources. Don’t you consider yourself too genius?
2. Do not ask questions and leave just as it is. As for me – you do not think of the future.
3. Ask questions and turn your attention to the other task. Choosing this option you waste your time and exert yourself with context switching, leaving one “stream” and entering the other. This is wasteful.
4. Ask questions and wait for all answers. And do nothing. That’s not for me. Idleness starts to torment me and I lose my self-respect.
5. Call for RS to be improved and forget about it. “Till all this bureaucracy shakes loose”. That is the least constructive way. If you choose it, think if you should be a developer. “Simple making money” can be somewhere else. For example, on Forex. Or other “bubbles”.
To forget is human. Everyone forgets. That’s axiomatic.
Managers, designers, analysts, customers (stakeholders, to be brief) – sometime do not answer ALL questions put to them. That’s an assumption. You’re lucky if it is not your case Then, this post is not for you. Do not read it. Sleep in peace.
But! It often happens that when RS comes to developers, it raises questions.
Do your job honest.
There is nothing worse than the unclarified questions hidden deep in your code of genius. All the same no one will appreciate its value.
Have respect for your colleagues coming in about “ten years after you”.
ASK QUESTIONS!
And STRIVE to get answers.
If during the implementation of RS you have a question that calls up stakeholders to answer, then, in order to maintain the “stream” state and to keep up the pace of the developing, I recommend you the following:
1. Ask the question.
2. MAKE SURE it reaches addressee.
3. Write an ASSERT. Yes, right! The trivial assert with a condition you don’t understand.
4. Formulate the HEART of the question in the commentaries to the code.
5. Name the DATE and time of the question. On CVS it is tedious and we are too lazy to do it.
6. Name the number of “ticket” in data base or give a reference to the discussion if it takes place electronically.
7. Prepare rather meaningful message to log for user version of the system. DISABLE ASSERTs in user version of the system.
8. Write - EXIT. In case further you have written another code of genius.
9. Wait for the answer and continue to implement RS.
10. If after a “reasonable” period of time you don’t get the answer – update your commentary to the code with details about “when, who and where”.
11. If you do not get answers – close the task and delegate it to Quality Control Group. It is better to give the list of unclarified details. May be the QCG will help you and roll back the task giving the details.
12. If you get the answer, say “thank you” to the one that gives it. He has “done part of your work”.
13. Delete the ASSERT.
14. Write a sensible code taking the answer into account.
15. Write a test. This aspect of the system has taken time and nerves of lots of people. It already WORTH being covered with test.
16. Sleep in peace till some ASSERT wakes you up.
Try it. May be you will like it.
I warn you that stakeholder may not like it. Anyway, I’d try.
After all – they do their job well. Honest. THINK of those who will come to replace you. They may be not that genius as you are.
And if you think – “oh, I have 800 unfinished tasks, oh, this is too hard for me” – look here:
That’s just an self-irony, you know…
P.S. There are alternatives. But, as for me, they are not that effective.
That’s how I see them:
1. Do not ask questions and rely on your resources. Don’t you consider yourself too genius?
2. Do not ask questions and leave just as it is. As for me – you do not think of the future.
3. Ask questions and turn your attention to the other task. Choosing this option you waste your time and exert yourself with context switching, leaving one “stream” and entering the other. This is wasteful.
4. Ask questions and wait for all answers. And do nothing. That’s not for me. Idleness starts to torment me and I lose my self-respect.
5. Call for RS to be improved and forget about it. “Till all this bureaucracy shakes loose”. That is the least constructive way. If you choose it, think if you should be a developer. “Simple making money” can be somewhere else. For example, on Forex. Or other “bubbles”.
Briefly. About factories
In a certain sense following on the theme raised here - Briefly. About triggering the exceptions
Partly following on this theme - Factory Method
How can we create an object?
Of course, in this way:
Or in this way:
What is the difference?
The difference is that in "fabric method" certain business logic can be introduced.
In our case it is - IsValidData(aSomeData).
But we can go further:
And then more further:
And even more further:
And that is all.
I hope somebody will like it.
Again I will mention it is only a “model”.
Oh!
One more brief remark.
How can we ensure that we have not “passed the factory”?
I mean ensure that not to call the “parasite default constructor” from TObject.
We can do it in this way:
We can also do it more “fun”, in this way:
In this case error code will not be compiled at all.
Probably, it can be done in this way:
Discussion - https://plus.google.com/u/0/113567376800896602748/posts/gkD3a9D1HXp
Partly following on this theme - Factory Method
How can we create an object?
Of course, in this way:
type TmyObject = class public constructor Create(aSomeData: TSomeData); end;//TmyObject ... var myObject : TmyObject; ... myObject := TmyObject.Create(aSomeData);
Or in this way:
type TmyObject = class protected constructor Create(aSomeData: TSomeData); public class function Make(aSomeData: TSomeData): TmyObject; end;//TmyObject ... class function TmyObject.Make(aSomeData: TSomeData): TmyObject; begin if IsValidData(aSomeData) then Result := Self.Create(aSomeData) else Result := nil; end; ... var myObject : TmyObject; ... myObject := TmyObject.Make(theConcreteData);
What is the difference?
The difference is that in "fabric method" certain business logic can be introduced.
In our case it is - IsValidData(aSomeData).
But we can go further:
interface ... type TmyObject = class protected constructor Create(aSomeData: TSomeData); procedure SomeMethodToOverride; virtual; public class function Make(aSomeData: TSomeData): TmyObject; end;//TmyObject ... implementation ... TmySpecialObject = class(TmyObject) protected procedure SomeMethodToOverride; override; end;//TmySpecialObject ... class function TmyObject.Make(aSomeData: TSomeData): TmyObject; begin if IsMySpecialData(aSomeData) then Result := TmySpecialObject.Create(aSomeData) else if IsValidData(aSomeData) then Result := Self.Create(aSomeData) else Result := nil; end; ... var myObject : TmyObject; ... myObject := TmyObject.Make(theConcreteData);
And then more further:
interface ... type TmyObject = class protected constructor Create(aSomeData: TSomeData); procedure SomeMethodToOverride; virtual; public class function Make(aSomeData: TSomeData): TmyObject; end;//TmyObject ... implementation ... TmyNULLObject = class(TmyObject) protected procedure SomeMethodToOverride; override; end;//TmyNULLObject TmySpecialObject = class(TmyObject) protected procedure SomeMethodToOverride; override; end;//TmySpecialObject ... class function TmyObject.Make(aSomeData: TSomeData): TmyObject; begin if IsMySpecialData(aSomeData) then Result := TmySpecialObject.Create(aSomeData) else if IsValidData(aSomeData) then Result := Self.Create(aSomeData) else Result := TmyNULLObject.Create(aSomeData); end; ... var myObject : TmyObject; ... myObject := TmyObject.Make(theConcreteData);
And even more further:
interface ... type ImyInterface = interface procedure SomeMethodToOverride; end;//ImyInterface TmyObject = class(TIntefacedObject, ImyInterface) protected constructor Create(aSomeData: TSomeData); procedure SomeMethodToOverride; virtual; public class function Make(aSomeData: TSomeData): ImyInterface; end;//TmyObject ... implementation ... TmyNULLObject = class(TIntefacedObject, ImyInterface) protected procedure SomeMethodToOverride; // - Of course, we no longer need override here constructor Create(aSomeData: TSomeData); public class function Make(aSomeData: TSomeData): ImyInterface; // - and here, generally, we can apply singleton end;//TmyNULLObject TmySpecialObject = class(TmyObject) protected procedure SomeMethodToOverride; override; end;//TmySpecialObject ... class function TmyObject.Make(aSomeData: TSomeData): ImyInterface; begin if IsMySpecialData(aSomeData) then Result := TmySpecialObject.Create(aSomeData) else if IsValidData(aSomeData) then Result := Self.Create(aSomeData) else Result := TmyNULLObject.Make(aSomeData); end; ... var myObject : ImyInterface; ... myObject := TmyObject.Make(theConcreteData);
And that is all.
I hope somebody will like it.
Again I will mention it is only a “model”.
Oh!
One more brief remark.
How can we ensure that we have not “passed the factory”?
I mean ensure that not to call the “parasite default constructor” from TObject.
We can do it in this way:
type TmyObject = class protected constructor Create(aSomeData: TSomeData); overload; public class function Make(aSomeData: TSomeData): TmyObject; constructor Create; overload; end;//TmyObject ... constructor TmyObject.Create; begin Assert(false, 'We should call the factory method, and not the inherited constructor'); end;
We can also do it more “fun”, in this way:
type TmyObject = class protected constructor InternalCreate(aSomeData: TSomeData); public class function Make(aSomeData: TSomeData): TmyObject; procedure Create; end;//TmyObject ... procedure TmyObject.Create; begin Assert(false, 'We should call the factory method, and not the inherited constructor'); end;
In this case error code will not be compiled at all.
Probably, it can be done in this way:
type TmyObject = class protected constructor InternalCreate(aSomeData: TSomeData); public class function Create(aSomeData: TSomeData): TmyObject; end;//TmyObject
Discussion - https://plus.google.com/u/0/113567376800896602748/posts/gkD3a9D1HXp
вторник, 24 февраля 2015 г.
How to keep labor productivity up and not to lose self-respect
Original in Russian: http://18delphi.blogspot.ru/2013/04/blog-post_2.html
Every day, from the morning, going to work by bus, I set myself a TASK FOR A DAY. I have to make it.
It can be to:
1. Write a debugged class.
2. Fix an error.
3. Write a test.
4. Implement requirement specification.
5. Draw UML-diagram.
6. Write documentation.
7. Make RS more exact resulting in concrete artifacts.
At least something in the list. Or better – a few of them. Chatter in CQ and e-mail do not enter the list. As well as internet surfing. As well as idle talks at meetings. What is important is the RESULT, not the process.
These must be artefacts put in CVS or SVN.
If the day passed without it – I think it is spent in vain.
Fire And Motion.
http://www.joelonsoftware.com/articles/fog0000000339.html
http://www.joelonsoftware.com/articles/fog0000000056.html
Try it. May be you will like it.
Every day, from the morning, going to work by bus, I set myself a TASK FOR A DAY. I have to make it.
It can be to:
1. Write a debugged class.
2. Fix an error.
3. Write a test.
4. Implement requirement specification.
5. Draw UML-diagram.
6. Write documentation.
7. Make RS more exact resulting in concrete artifacts.
At least something in the list. Or better – a few of them. Chatter in CQ and e-mail do not enter the list. As well as internet surfing. As well as idle talks at meetings. What is important is the RESULT, not the process.
These must be artefacts put in CVS or SVN.
If the day passed without it – I think it is spent in vain.
Fire And Motion.
http://www.joelonsoftware.com/articles/fog0000000339.html
http://www.joelonsoftware.com/articles/fog0000000056.html
Try it. May be you will like it.
понедельник, 23 февраля 2015 г.
What else I want to tell about TDD (not finished)
Original in Russian: http://programmingmindstream.blogspot.ru/2013/11/tdd_28.html
What else I want to tell about TDD
The previous series was here - http://programmingmindstream.blogspot.ru/2013/11/tdd.html
Many people I’ve communicated with about tests in general and about TDD in particular “crashed” on the following difficulty:
TestFirst. TestDriven.
I don’t want to “argue”, much less to disprove or criticize any of respected "theorists of TDD".nbsp;
I just want to describe my interpretation. Or – how I have understood it.
TDD says just about the following:
1. Launch ALL tests, make sure they has been done.
2. Add new test for new functionality. Make sure it has not been done.
3. Add functionality. Make sure the test has been done.
What question arises immediately?
This one: “How can I write a test for something that DOES NOT EXIST?”?
That is a FAIR question.
When I looked at TDD for the first time, I thought: - "what a bullshit" (that is an “epithet”, not an “foul term”);
How can I write test for something that DOES NOT EXIST? Let it be even NOT appropriate.
“A lady gave me a gift she had not,
And I received her gift I took not...”
I have been considering the matter for a long time and that is what I’ve decided.
There is NO TestFirst.
There is NO TestFirst.
In the FIRST place – there is a “draft” of the ARCHITECTURE. Neither code, nor tests, nor anything else, but – the “draft” of the ARCHITECTURE.
And TestFirst is made not for something “abstract”, that we’ll have “sometime later”, but for a VERY SPECIAL purpose.
Described in the architecture and having legs growing from requirements specification (RS).
The simplest example:
Let’s say that you need to create a “button” that brings new functionality.
Then, test can be as follows:
And, OF COURSE, it won’t be done- until we add a button and will be done – WHEN we finally ADD it.
But! The test "KNOWS" that the button exists. At least, it knows its name. It means it doesn’t test “whatever”, but something certain. A part of architecture concepts. A BUTTON, described in requirements specification and in the architecture.
Let’s move on.
For example, we have to write TmyIntegerList class (I’ll analyze it in detail below).
What do we do?
We write:
“The design of the architecture” and a draft of the designed class.
EXACTLY these, but not the TEST.
Once again:
We write: “The design of the architecture” and a draft of the designed class.
EXACTLY these, but not the TEST.
Something like this:
Only AFTER THAT, we write TEST.
Once AGAIN – ONLY after we write TEST.
Let us say, of this kind:
And this test – OF COURSE – WILL NOT BE DONE. And THEN, when we add IMPLEMENTATION of the method TmyIntegerList. Add – it will be done.
So.
There is no any TestFirst.
When speaking of "first" – the architecture and the design are PRIMARY, only after – the TESTS. And only after - the implementation.
So the chain of developing looks like this:
RS -> Draft of architecture -> Test -> Code
Then, there are all possible options:
RS -> Draft of architecture -> Test -> Code -> Test -> Code
RS -> Draft of architecture -> Test -> Code -> Architecture -> Test -> Code
RS -> Draft of architecture -> Test -> Code -> Architecture -> Test -> Code -> RS -> Architecture -> Test -> Code
and so on and so forth
First is - RS and the draft of the architecture – then begins – “iteration development”.
Especially delicious is that, as soon as we have written the test, we DON’T HAVE to think about “where can we check the functionability of our class”.
We have ALREADY configured all infrastructure to test it and to check.
EXACTLY this is what the word Driven in TDD means.
TESTS – HELP but not INTERFERE with the process of development.
The developer is “lead” by the tests.
Tests influence the code, and the code influences the tests.
In turn, these two TOGETHER influence the architecture and RS.
Most important is that if the tests “lead”, then, nevertheless, one way or another they are – written and they check the functionability of designed classes. It means there is NOT THE SLIGHTEST reason not to use them.
Even if you write something like this:
Class TmyIntegerList is “kind of” not used, but it doesn’t mean that class TmyIntegerList doesn’t “exist in nature”. It exists, at least, “in your head”. If you’ve written something like TmyIntegerListTests.ListAdd, it MEANS, there is something having operation Add.
If you say to me “phew… the list of the integers… how banal… test for us the nuclear reactor…”
I’d answer - "yes, banal". But! “A journey of a thousand miles started with a first step”. And the “nuclear reactors” somewhere inside “consist of the lists of integers”.
If you’d like the REAL examples of the tests – I’ve got them. A WHOLE lot of them. I’m ready to show “in exchange for” your “at least one test”, so that to “SPEAK ON EQUAL TERMS” instead of bringing grist to the non-working mill.
Now, the remark about where, on my humble opinion, the notion of TestFirst is taken from and about “first you test what does not exist”.
It seems to me that the thing is everything comes from Java and JUnit, where reflection, duck-typing, injections and other “tricks” of this kind are used vastly.
There you can write “sort of” this test:
I am not a great expert on Java, but I hope the idea is clear.
Here, you get the “wag the dog” situation. We “kind of” know NOTHING about the tested class but, however, we can “test” it without writing it first. TestFirst. Sort of…
But let’s think – “do we really know nothing about the tested class?” OF COURSE, NO. We know its name and the fact that it has method Add.
TestFirst? Not on your life! ArchitectureFirst!!!
It does not matter that we have NOT written A SINGLE line of our class – we have ALREADY started to DESIGN its ARCHITECTURE, ONLY after – STARTED to write the test.
It does not matter that we have NOT written A SINGLE line of the code, except from the test, but! “The design of the architecture” – EXISTS when approaching with reflection or duck-typing. It EXISTS – AT LEAST “in our heads”.
But it EXISTS!
ArchitectureFirst!!!
I hope that my thought is understandable and denies all doubts about “How can I write test for something that does NOT EXIST?”
Not something that “does not exist”! But something that is being “developed and designed”. I hope it is clear.
And this does not mean that I “criticize” TDD. Quite the contrary - I am trying to do my best to ensure that as many as possible programmers are inspired with this idea.
I am trying to “deny doubts” and show that it is “not all that complicated”.
Now I will venture to give an example of “how I usually do”.
In the example I will keep to the MOST undesirable and pessimistic scenario – when either the customer or “Quality Control Group” ignore the questions of the developer.
So, the developer has either to “think up on his own”, or to write Asserts - http://18delphi.blogspot.ru/2013/04/blog-post.html
The whole code of the example is available in SVN here - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/TDD/Chapter0/
Let us say, we have to write the same class TmyIntegerList.
Let’s say, its specification is as follows:
TmyIntegerList is a list of integers.
Supports operations of:
1. Inserting an item.
2. Adding an item.
3. Deleting an item.
4. Getting the number of items.
5. Getting the value of the item by its index number.
This is “sort of” - RS.
Let’s write the “first draft” of the test:
-- it this draft we “kind of do not know” WHAT we test, but, in fact, the “draft of the architecture” is ALREADY sitting “in our head”. We know about the methods Add, Insert, Delete, Count and Item.
And, of course, the test is not done.
Let us state the “draft of the architecture” in the designed class.
Exactly – let us “put on paper” what “was in our brain” when we’ve been writing the first test.
Let’s describe the “prototype” of our class:
And let’s modify the tests:
-- at this point, we already KNOW about the REAL designed class and use it.
Tests in this state SURELY will not be done.
Let’s move on.
Let’s implement at least one of the methods of our designed class.
Or rather – TWO of them, the simplest ones - Add and Count.
That’s how it looks like:
-- we’ll see that two tests – have been DONE. Not necessarily correctly done, but they has been DONE.
Here we happen to deal with what is called Driven.
We’ve filled the “skeleton” of the prototype with the “flesh” and got a response from tests at once.
Let’s move on.
Let’s implement the other methods of our designed class:
We launch the tests, and what do we see?
ONE test – has NOT been done - TmyIntegerListTests.ListItem - AV has occurred there.
The reason is in TmyIntegerList.pm_GetItem method.
What are we to do?
Let’s rewrite method TmyIntegerList.pm_GetItem in this way:
We launch the tests and see – they have been DONE!
Have we done our work? "Probably". But not a fact.
Suppose, we have sent our application to “Quality Control Group” and it found the following:
And “opened the ticket in QC with number 1”.
After having “got the details” and the negotiations with QCG – we make sure that, however, there is an error.
We write the NEW TEST:
And we make sure that, however , it is NOT done!
What are we to do?
We edit the code of the designed class:
We launch the tests - and they are done. Have we done our work? I DON’T KNOW. Probably…
Now let’s suppose, our QCG has found one more error:
... to be continued ...
Should I continue? Or the idea is clear?
----
Discussion - https://plus.google.com/u/0/113567376800896602748/posts/8pG2gJNkG7F
What else I want to tell about TDD
The previous series was here - http://programmingmindstream.blogspot.ru/2013/11/tdd.html
Many people I’ve communicated with about tests in general and about TDD in particular “crashed” on the following difficulty:
TestFirst. TestDriven.
I don’t want to “argue”, much less to disprove or criticize any of respected "theorists of TDD".nbsp;
I just want to describe my interpretation. Or – how I have understood it.
TDD says just about the following:
1. Launch ALL tests, make sure they has been done.
2. Add new test for new functionality. Make sure it has not been done.
3. Add functionality. Make sure the test has been done.
What question arises immediately?
This one: “How can I write a test for something that DOES NOT EXIST?”?
That is a FAIR question.
When I looked at TDD for the first time, I thought: - "what a bullshit" (that is an “epithet”, not an “foul term”);
How can I write test for something that DOES NOT EXIST? Let it be even NOT appropriate.
“A lady gave me a gift she had not,
And I received her gift I took not...”
I have been considering the matter for a long time and that is what I’ve decided.
There is NO TestFirst.
There is NO TestFirst.
In the FIRST place – there is a “draft” of the ARCHITECTURE. Neither code, nor tests, nor anything else, but – the “draft” of the ARCHITECTURE.
And TestFirst is made not for something “abstract”, that we’ll have “sometime later”, but for a VERY SPECIAL purpose.
Described in the architecture and having legs growing from requirements specification (RS).
The simplest example:
Let’s say that you need to create a “button” that brings new functionality.
Then, test can be as follows:
AssureThatButtonExists('SomeButton');
And, OF COURSE, it won’t be done- until we add a button and will be done – WHEN we finally ADD it.
But! The test "KNOWS" that the button exists. At least, it knows its name. It means it doesn’t test “whatever”, but something certain. A part of architecture concepts. A BUTTON, described in requirements specification and in the architecture.
Let’s move on.
For example, we have to write TmyIntegerList class (I’ll analyze it in detail below).
What do we do?
We write:
“The design of the architecture” and a draft of the designed class.
EXACTLY these, but not the TEST.
Once again:
We write: “The design of the architecture” and a draft of the designed class.
EXACTLY these, but not the TEST.
Something like this:
type TmyIntegerList = class public procedure Add(anItem: Integer); end;//TmyIntegerList procedure TmyIntegerList.Add(anItem: Integer); begin Assert(false, 'Not implemented'); end;
Only AFTER THAT, we write TEST.
Once AGAIN – ONLY after we write TEST.
Let us say, of this kind:
procedure TmyIntegerListTest.ListAdd; var l_List : TmyIntegerList; begin l_List := TmyIntegerList.Create; try l_List.Add(47879); finally FreeAndNil(l_List); end; end;
And this test – OF COURSE – WILL NOT BE DONE. And THEN, when we add IMPLEMENTATION of the method TmyIntegerList. Add – it will be done.
So.
There is no any TestFirst.
When speaking of "first" – the architecture and the design are PRIMARY, only after – the TESTS. And only after - the implementation.
So the chain of developing looks like this:
RS -> Draft of architecture -> Test -> Code
Then, there are all possible options:
RS -> Draft of architecture -> Test -> Code -> Test -> Code
RS -> Draft of architecture -> Test -> Code -> Architecture -> Test -> Code
RS -> Draft of architecture -> Test -> Code -> Architecture -> Test -> Code -> RS -> Architecture -> Test -> Code
and so on and so forth
First is - RS and the draft of the architecture – then begins – “iteration development”.
Especially delicious is that, as soon as we have written the test, we DON’T HAVE to think about “where can we check the functionability of our class”.
We have ALREADY configured all infrastructure to test it and to check.
EXACTLY this is what the word Driven in TDD means.
TESTS – HELP but not INTERFERE with the process of development.
The developer is “lead” by the tests.
Tests influence the code, and the code influences the tests.
In turn, these two TOGETHER influence the architecture and RS.
Most important is that if the tests “lead”, then, nevertheless, one way or another they are – written and they check the functionability of designed classes. It means there is NOT THE SLIGHTEST reason not to use them.
Even if you write something like this:
unit myListTests; interface uses TestFramework ; type TmyIntegerListTests = class(TTestCase) published procedure ListAdd; end;//TmyIntegerListTests implementation procedure TmyIntegerListTests.ListAdd; begin Assert(false, 'Not implemented'); end; initialization TestFramework.RegisterTest(TmyIntegerListTests.Suite); end.
Class TmyIntegerList is “kind of” not used, but it doesn’t mean that class TmyIntegerList doesn’t “exist in nature”. It exists, at least, “in your head”. If you’ve written something like TmyIntegerListTests.ListAdd, it MEANS, there is something having operation Add.
If you say to me “phew… the list of the integers… how banal… test for us the nuclear reactor…”
I’d answer - "yes, banal". But! “A journey of a thousand miles started with a first step”. And the “nuclear reactors” somewhere inside “consist of the lists of integers”.
If you’d like the REAL examples of the tests – I’ve got them. A WHOLE lot of them. I’m ready to show “in exchange for” your “at least one test”, so that to “SPEAK ON EQUAL TERMS” instead of bringing grist to the non-working mill.
Now, the remark about where, on my humble opinion, the notion of TestFirst is taken from and about “first you test what does not exist”.
It seems to me that the thing is everything comes from Java and JUnit, where reflection, duck-typing, injections and other “tricks” of this kind are used vastly.
There you can write “sort of” this test:
procedure TmyIntegerListTest.ListAdd;
var
l_List : Object;
begin
l_List := Framework.GetClassByName('TmyIntegerList').Create;
try
l_List.MethodByName.Execute('Add', [47879]);
finally
FreeAndNil(l_List);
end;
end;
I am not a great expert on Java, but I hope the idea is clear.
Here, you get the “wag the dog” situation. We “kind of” know NOTHING about the tested class but, however, we can “test” it without writing it first. TestFirst. Sort of…
But let’s think – “do we really know nothing about the tested class?” OF COURSE, NO. We know its name and the fact that it has method Add.
TestFirst? Not on your life! ArchitectureFirst!!!
It does not matter that we have NOT written A SINGLE line of our class – we have ALREADY started to DESIGN its ARCHITECTURE, ONLY after – STARTED to write the test.
It does not matter that we have NOT written A SINGLE line of the code, except from the test, but! “The design of the architecture” – EXISTS when approaching with reflection or duck-typing. It EXISTS – AT LEAST “in our heads”.
But it EXISTS!
ArchitectureFirst!!!
I hope that my thought is understandable and denies all doubts about “How can I write test for something that does NOT EXIST?”
Not something that “does not exist”! But something that is being “developed and designed”. I hope it is clear.
And this does not mean that I “criticize” TDD. Quite the contrary - I am trying to do my best to ensure that as many as possible programmers are inspired with this idea.
Now I will venture to give an example of “how I usually do”.
In the example I will keep to the MOST undesirable and pessimistic scenario – when either the customer or “Quality Control Group” ignore the questions of the developer.
So, the developer has either to “think up on his own”, or to write Asserts - http://18delphi.blogspot.ru/2013/04/blog-post.html
The whole code of the example is available in SVN here - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/TDD/Chapter0/
Let us say, we have to write the same class TmyIntegerList.
Let’s say, its specification is as follows:
TmyIntegerList is a list of integers.
Supports operations of:
1. Inserting an item.
2. Adding an item.
3. Deleting an item.
4. Getting the number of items.
5. Getting the value of the item by its index number.
This is “sort of” - RS.
Let’s write the “first draft” of the test:
program TDD;
uses
Vcl.Forms,
GUITestRunner,
myListTests in 'Tests\myListTests.pas';
{$R *.res}
begin
Application.Initialize;
GUITestRunner.RunRegisteredTests;
end.
unit myListTests;
interface
uses
TestFramework
;
type
TmyIntegerListTests = class(TTestCase)
published
procedure ListAdd;
procedure ListInsert;
procedure ListDelete;
procedure ListCount;
procedure ListItem;
end;//TmyIntegerListTests
implementation
procedure TmyIntegerListTests.ListAdd;
begin
Assert(false, 'Not implemented');
end;
procedure TmyIntegerListTests.ListInsert;
begin
Assert(false, 'Not implemented');
end;
procedure TmyIntegerListTests.ListDelete;
begin
Assert(false, 'Not implemented');
end;
procedure TmyIntegerListTests.ListCount;
begin
Assert(false, 'Not implemented');
end;
procedure TmyIntegerListTests.ListItem;
begin
Assert(false, 'Not implemented');
end;
initialization
TestFramework.RegisterTest(TmyIntegerListTests.Suite);
end.
-- it this draft we “kind of do not know” WHAT we test, but, in fact, the “draft of the architecture” is ALREADY sitting “in our head”. We know about the methods Add, Insert, Delete, Count and Item.
And, of course, the test is not done.
Let us state the “draft of the architecture” in the designed class.
Exactly – let us “put on paper” what “was in our brain” when we’ve been writing the first test.
Let’s describe the “prototype” of our class:
unit myIntegerList;
interface
type
TmyIntegerList = class
public
type
IndexType = Integer;
ItemType = Integer;
protected
function pm_GetCount: IndexType;
function pm_GetItem(anIndex: IndexType): ItemType;
public
procedure Add(anItem: ItemType);
procedure Insert(anIndex: IndexType; anItem: ItemType);
procedure Delete(anIndex: IndexType);
property Count: IndexType
read pm_GetCount;
property Items[anIndex: IndexType]: ItemType
read pm_GetItem;
end;//TmyIntegerList
implementation
// TmyIntegerList
function TmyIntegerList.pm_GetCount: IndexType;
begin
Result := -1;
Assert(false, 'Not implemented');
end;
function TmyIntegerList.pm_GetItem(anIndex: IndexType): ItemType;
begin
Result := -1;
Assert(false, 'Not implemented');
end;
procedure TmyIntegerList.Add(anItem: ItemType);
begin
Assert(false, 'Not implemented');
end;
procedure TmyIntegerList.Insert(anIndex: IndexType; anItem: ItemType);
begin
Assert(false, 'Not implemented');
end;
procedure TmyIntegerList.Delete(anIndex: IndexType);
begin
Assert(false, 'Not implemented');
end;
end.
And let’s modify the tests:
unit myIntegerListTests; interface uses TestFramework ; type TmyIntegerListTests = class(TTestCase) published procedure ListAdd; procedure ListInsert; procedure ListDelete; procedure ListCount; procedure ListItem; end;//TmyIntegerListTests implementation uses System.SysUtils, myIntegerList ; procedure TmyIntegerListTests.ListAdd; var l_List : TmyIntegerList; begin l_List := TmyIntegerList.Create; try l_List.Add(Random(1000)); finally FreeAndNil(l_List); end;//try..finally end; procedure TmyIntegerListTests.ListInsert; var l_List : TmyIntegerList; begin l_List := TmyIntegerList.Create; try l_List.Insert(0, Random(1000)); finally FreeAndNil(l_List); end;//try..finally end; procedure TmyIntegerListTests.ListDelete; var l_List : TmyIntegerList; begin l_List := TmyIntegerList.Create; try l_List.Delete(0); finally FreeAndNil(l_List); end;//try..finally end; procedure TmyIntegerListTests.ListCount; var l_List : TmyIntegerList; begin l_List := TmyIntegerList.Create; try l_List.Count; finally FreeAndNil(l_List); end;//try..finally end; procedure TmyIntegerListTests.ListItem; var l_List : TmyIntegerList; begin l_List := TmyIntegerList.Create; try l_List.Item[0]; finally FreeAndNil(l_List); end;//try..finally end; initialization TestFramework.RegisterTest(TmyIntegerListTests.Suite); end.
-- at this point, we already KNOW about the REAL designed class and use it.
Tests in this state SURELY will not be done.
Let’s move on.
Let’s implement at least one of the methods of our designed class.
Or rather – TWO of them, the simplest ones - Add and Count.
That’s how it looks like:
unit myIntegerList;
interface
type
TmyIntegerList = class
public
type
IndexType = Integer;
ItemType = Integer;
private
type
ItemsArray = array of ItemType;
private
f_Items : ItemsArray;
protected
function pm_GetCount: IndexType;
function pm_GetItem(anIndex: IndexType): ItemType;
public
procedure Add(anItem: ItemType);
procedure Insert(anIndex: IndexType; anItem: ItemType);
procedure Delete(anIndex: IndexType);
property Count: IndexType
read pm_GetCount;
property Item[anIndex: IndexType]: ItemType
read pm_GetItem;
end;//TmyIntegerList
implementation
// TmyIntegerList
function TmyIntegerList.pm_GetCount: IndexType;
begin
Result := Length(f_Items);
end;
function TmyIntegerList.pm_GetItem(anIndex: IndexType): ItemType;
begin
Result := -1;
Assert(false, 'Not implemented');
end;
procedure TmyIntegerList.Add(anItem: ItemType);
begin
SetLength(f_Items, Length(f_Items) + 1);
f_Items[High(f_Items)] := anItem;
end;
procedure TmyIntegerList.Insert(anIndex: IndexType; anItem: ItemType);
begin
Assert(false, 'Not implemented');
end;
procedure TmyIntegerList.Delete(anIndex: IndexType);
begin
Assert(false, 'Not implemented');
end;
end.
-- we’ll see that two tests – have been DONE. Not necessarily correctly done, but they has been DONE.
Here we happen to deal with what is called Driven.
We’ve filled the “skeleton” of the prototype with the “flesh” and got a response from tests at once.
Let’s move on.
Let’s implement the other methods of our designed class:
unit myIntegerList;
interface
type
TmyIntegerList = class
public
type
IndexType = Integer;
ItemType = Integer;
private
type
ItemsArray = array of ItemType;
private
f_Items : ItemsArray;
protected
function pm_GetCount: IndexType;
function pm_GetItem(anIndex: IndexType): ItemType;
public
procedure Add(anItem: ItemType);
procedure Insert(anIndex: IndexType; anItem: ItemType);
procedure Delete(anIndex: IndexType);
property Count: IndexType
read pm_GetCount;
property Item[anIndex: IndexType]: ItemType
read pm_GetItem;
end;//TmyIntegerList
implementation
// TmyIntegerList
function TmyIntegerList.pm_GetCount: IndexType;
begin
Result := Length(f_Items);
end;
function TmyIntegerList.pm_GetItem(anIndex: IndexType): ItemType;
begin
Result := f_Items[anIndex];
end;
procedure TmyIntegerList.Add(anItem: ItemType);
begin
SetLength(f_Items, Length(f_Items) + 1);
f_Items[High(f_Items)] := anItem;
end;
procedure TmyIntegerList.Insert(anIndex: IndexType; anItem: ItemType);
begin
if (anIndex = Self.Count) then
Add(anItem)
else
Assert(false, 'Not implemented');
// - I really don’t know what I am to do here since RS is kind of INCOMPLETE
end;
procedure TmyIntegerList.Delete(anIndex: IndexType);
begin
if (anIndex < 0) OR (anIndex >= Self.Count) then
// - there is really nothing to delete
Exit
else
Assert(false, 'Not implemented');
end;
end.
We launch the tests, and what do we see?
ONE test – has NOT been done - TmyIntegerListTests.ListItem - AV has occurred there.
The reason is in TmyIntegerList.pm_GetItem method.
What are we to do?
Let’s rewrite method TmyIntegerList.pm_GetItem in this way:
function TmyIntegerList.pm_GetItem(anIndex: IndexType): ItemType; begin if (Self.Count = 0) then // - I really don’t know what to do since RS does not specify it Result := Random(5676) else Result := f_Items[anIndex]; end;
We launch the tests and see – they have been DONE!
Have we done our work? "Probably". But not a fact.
Suppose, we have sent our application to “Quality Control Group” and it found the following:
var l_List : TmyIntegerList; begin l_List := TmyIntegerList.Create; try l_List.Add(Random(54365)); l_List.Delete(0); finally FreeAndNil(l_List); end;//try..finally end;
And “opened the ticket in QC with number 1”.
After having “got the details” and the negotiations with QCG – we make sure that, however, there is an error.
We write the NEW TEST:
procedure TmyIntegerListTests.QCTicket1; var l_List : TmyIntegerList; begin l_List := TmyIntegerList.Create; try l_List.Add(Random(54365)); l_List.Delete(0); finally FreeAndNil(l_List); end;//try..finally end;
And we make sure that, however , it is NOT done!
What are we to do?
We edit the code of the designed class:
procedure TmyIntegerList.Delete(anIndex: IndexType); begin if (anIndex < 0) OR (anIndex >= Self.Count) then // - there’s really nothing to delete Exit else if (anIndex = Self.Count - 1) then SetLength(f_Items, Self.Count - 1) else Assert(false, 'I really don’t know what am I to do here'); end;
We launch the tests - and they are done. Have we done our work? I DON’T KNOW. Probably…
Now let’s suppose, our QCG has found one more error:
... to be continued ...
Should I continue? Or the idea is clear?
----
Discussion - https://plus.google.com/u/0/113567376800896602748/posts/8pG2gJNkG7F
Once again about "testing levels"
Original in Russian: http://programmingmindstream.blogspot.ru/2014/01/blog-post_27.html
I have written a bit here - http://18delphi.blogspot.ru/2013/11/gui_9423.html
I will follow up on it.
Many say “oh, we have to test” (Many more say, of course, "there is no need in testing” :-) But this post is not for them).
Let’s ask ourselves - "what exactly is to be tested"?
I’ll try to pick out the "testing layers" and the "appropriate tools".
The choices are the following:
1. Testing of "base classes" and "data structure". The “bare” DUnit (http://dunit.sourceforge.net/) and "atomic tests" - http://18delphi.blogspot.ru/2013/04/blog-post_2326.html and http://programmingmindstream.blogspot.ru/2013/11/tdd_28.html are appropriate for using here. Don’t forget about the “pre-” and “postconditions”.
2. Testing of classes with "parameterized entry" (entry mock's). Here it is appropriate to use DUnit add-in - http://18delphi.blogspot.ru/2013/04/blog-post_7108.html and also - "the etalons" - http://18delphi.blogspot.ru/2013/04/blog-post_8791.html
3. Testing of “user stories” (of “base classes”). Here it is appropriate to use this - http://roman.yankovsky.me/?p=1355. May be, one can also test “use cases”, but Roman has not yet developed this theme.
4. Testing of “separate components". Here this one is appropriate - http://programmingmindstream.blogspot.ru/2014/01/openctf-component-test-framework.html
5. Testing of “use cases". Here this one can be used - http://18delphi.blogspot.com/2015/03/gui-testing-11-gui-testing-in-spoken.html and http://18delphi.blogspot.ru/2013/11/gui-back-to-basics_22.html and http://18delphi.blogspot.ru/2013/11/gui-dunit.html
6. Testing of GUI-application as a “black” (“gray”) box (“honest” pressing the “buttons”). Here it is appropriate to use - TestComplete (http://smartbear.com/products/qa-tools/automated-testing-tools/ ). Do not forget about - http://programmingmindstream.blogspot.ru/2014/01/blog-post_626.html (in particular, it is written about “gray box” there).
7. Testing of GUI-application as a “true user” (we do not trust "bots”). What here? Here – MANUAL TESTING is used. "No tools"... The only tool is a sort of RequisitePro (http://www-03.ibm.com/software/products/ru/reqpro/ ) and writing HLTC (www.qualitytesting.info/forum/topics/low-and-high-level-test-cases?commentId=2064344%3AComment%3A427950&xg_source=activity and http://pic.dhe.ibm.com/infocenter/rqmhelp/v2r0/index.jsp?topic=/com.ibm.rational.test.qm.doc/topics/c_testcases.html and http://www.sqaforums.com/showflat.php?Number=663587) for "live testers".
It is so somehow...
Should I develop the theme?
P.S. What else do I want to tell?
The PREFERENCE of tests increases when moving from item 7 to item 1.
Why?
Because the COMPLEXITY of tests increases in the REVERSE order - when moving from item 1 to item 7.
That is why (other things equal), one should first try to write a test of a “lower level” and then (if it “goes wrong”) – to increase the level.
ONE more (very important) EXEPTION – if “similar test” has ALREADY been WRITTEN. In this case, the testing level is – NOT IMPORTANT. You have to take the ALREADY EXISTING test and try to ADOPT it to the “new tested case”.
I have written a bit here - http://18delphi.blogspot.ru/2013/11/gui_9423.html
I will follow up on it.
Many say “oh, we have to test” (Many more say, of course, "there is no need in testing” :-) But this post is not for them).
Let’s ask ourselves - "what exactly is to be tested"?
I’ll try to pick out the "testing layers" and the "appropriate tools".
The choices are the following:
1. Testing of "base classes" and "data structure". The “bare” DUnit (http://dunit.sourceforge.net/) and "atomic tests" - http://18delphi.blogspot.ru/2013/04/blog-post_2326.html and http://programmingmindstream.blogspot.ru/2013/11/tdd_28.html are appropriate for using here. Don’t forget about the “pre-” and “postconditions”.
2. Testing of classes with "parameterized entry" (entry mock's). Here it is appropriate to use DUnit add-in - http://18delphi.blogspot.ru/2013/04/blog-post_7108.html and also - "the etalons" - http://18delphi.blogspot.ru/2013/04/blog-post_8791.html
3. Testing of “user stories” (of “base classes”). Here it is appropriate to use this - http://roman.yankovsky.me/?p=1355. May be, one can also test “use cases”, but Roman has not yet developed this theme.
4. Testing of “separate components". Here this one is appropriate - http://programmingmindstream.blogspot.ru/2014/01/openctf-component-test-framework.html
5. Testing of “use cases". Here this one can be used - http://18delphi.blogspot.com/2015/03/gui-testing-11-gui-testing-in-spoken.html and http://18delphi.blogspot.ru/2013/11/gui-back-to-basics_22.html and http://18delphi.blogspot.ru/2013/11/gui-dunit.html
6. Testing of GUI-application as a “black” (“gray”) box (“honest” pressing the “buttons”). Here it is appropriate to use - TestComplete (http://smartbear.com/products/qa-tools/automated-testing-tools/ ). Do not forget about - http://programmingmindstream.blogspot.ru/2014/01/blog-post_626.html (in particular, it is written about “gray box” there).
7. Testing of GUI-application as a “true user” (we do not trust "bots”). What here? Here – MANUAL TESTING is used. "No tools"... The only tool is a sort of RequisitePro (http://www-03.ibm.com/software/products/ru/reqpro/ ) and writing HLTC (www.qualitytesting.info/forum/topics/low-and-high-level-test-cases?commentId=2064344%3AComment%3A427950&xg_source=activity and http://pic.dhe.ibm.com/infocenter/rqmhelp/v2r0/index.jsp?topic=/com.ibm.rational.test.qm.doc/topics/c_testcases.html and http://www.sqaforums.com/showflat.php?Number=663587) for "live testers".
It is so somehow...
Should I develop the theme?
P.S. What else do I want to tell?
The PREFERENCE of tests increases when moving from item 7 to item 1.
Why?
Because the COMPLEXITY of tests increases in the REVERSE order - when moving from item 1 to item 7.
That is why (other things equal), one should first try to write a test of a “lower level” and then (if it “goes wrong”) – to increase the level.
ONE more (very important) EXEPTION – if “similar test” has ALREADY been WRITTEN. In this case, the testing level is – NOT IMPORTANT. You have to take the ALREADY EXISTING test and try to ADOPT it to the “new tested case”.
Подписаться на:
Сообщения (Atom)











