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
Комментариев нет:
Отправить комментарий