вторник, 1 сентября 2015 г.

#1133. Делаем тесты к скриптовым словам

Предыдущая серия была тут - Вводная. Делаем тесты к скриптовым словам.

Там "слова словаря" содержались отдельно, а тесты к ним - отдельно.

Теперь я свёл всё это воедино:

string.ms.dict:

https://bitbucket.org/lulinalex/mindstream/src/4cc7f0347a37bc378109843cce5b316044fa22d4/Examples/Scripts/CodeGeneration1/string.ms.dict?at=B284

// string.ms.dict.web

USES
 Documentation.ms.dict
 params.ms.dict
 core.ms.dict
 map.ms.dict
 Testing.ms.dict
 io.ms.dict
;

: (string)
 ^ IN aValue
 aValue DO ToPrintable
; // (string)

STRING FUNCTION string:CatWihAny
 STRING IN aString
 IN aValue
 aString aValue ToPrintable Cat =: Result
; // string:CatWihAny

STRING FUNCTION any:Cat
 ARRAY IN anArray
 anArray .map> ToPrintable strings:Cat =: Result
; // any:Cat

TestsFor any:Cat
 Test T1 [ 'A' 123 'B' ] any:Cat Print ;
 Test T2 [ 'A' 124 'B' ] any:Cat Print ;
; // TestsFor any:Cat

STRING FUNCTION (+)?
 STRING in aPrefix
 STRING right aSuffix
 %SUMMARY 'Если aSuffix не пустой, то возвращает сумму aPrefix и aSuffix, иначе возвращает пустую строку' ;
 Result := ''
 STRING VAR l_Suffix
 aSuffix =: l_Suffix
 if ( l_Suffix =/= '' ) then
  ( aPrefix l_Suffix Cat =: Result )
; // (+)?

TestsFor (+)?
 Test T1 '' (+)? 'B' Print ;
 Test T2 'A' (+)? 'B' Print ;
 Test T3 'A' (+)? '' Print ;
 Test T4 'A' (+)? 'D' Print ;
 Test T5 'A' (+)? '123' Print ;
; // TestsFor (+)?

STRING FUNCTION ?(+)
 STRING in aPrefix
 STRING right aSuffix
 %SUMMARY 'Если aPrefix не пустой, то возвращает сумму aPrefix и aSuffix, иначе возвращает пустую строку' ;
 Result := ''
 if ( aPrefix =/= '' ) then
  ( aPrefix aSuffix Cat =: Result )
; // ?(+)

TestsFor ?(+)
 Test T1 '' ?(+) 'B' Print ;
 Test T2 'A' ?(+) 'B' Print ;
 Test T3 'A' ?(+) '' Print ;
; // TestsFor ?(+)

STRING FUNCTION strings:CatSep>
 STRING right aSep
 ARRAY right aValues
 aValues aSep strings:CatSep =: Result
; // strings:CatSep>

TestsFor strings:CatSep>
 Test T1 strings:CatSep> ' ' [ 'A' 'B' ] Print ;
 Test T2 strings:CatSep> ' ' [ 'A ' 'B' ] Print ;
 Test T3 strings:CatSep> ' ' [ 'A ' ' B' ] Print ;
 Test T4 strings:CatSep> ' ' [ 'A' ' B' ] Print ;
 Test T5 strings:CatSep> ' ' [ '' 'B' ] Print ;
 Test T6 strings:CatSep> ' ' [ 'A' '' ] Print ;
; // TestsFor strings:CatSep>
 
WordAlias CatSep> strings:CatSep>

String.ms.script:

https://bitbucket.org/lulinalex/mindstream/src/4cc7f0347a37bc378109843cce5b316044fa22d4/Examples/Scripts/CodeGeneration1/String.ms.script?at=B284

USES
 Documentation.ms.dict
 string.ms.dict
 Testing.ms.dict
;

RunTests (+)?
 %REMARK 'Запускаем "стандартные тесты" для слова (+)?'
RunTests ?(+)
 %REMARK 'Запускаем "стандартные тесты" для слова ?(+)'
RunTests strings:CatSep>
 %REMARK 'Запускаем "стандартные тесты" для слова strings:CatSep>'
RunTests any:Cat
 %REMARK 'Запускаем "стандартные тесты" для слова any:Cat'

Ну и тестовый вывод:

String.ms.script.out

Testing: (+)?
T1
B
T2
AB
T3

T4
AD
T5
A123
Testing end: (+)?
------------------
Testing: ?(+)
T1

T2
AB
T3
A
Testing end: ?(+)
------------------
Testing: strings:CatSep>
T1
A B
T2
A B
T3
A  B
T4
A  B
T5
B
T6
A
Testing end: strings:CatSep>
------------------
Testing: any:Cat
T1
A123B
T2
A124B
Testing end: any:Cat
------------------


Для того, чтобы свести всё воедино" я ввёл слова - TestsFor и RunTests.

TestsFor - определяет тесты для слова.
RunTests - запускает тесты для слова.

Выглядят они так:

Testing.ms.dict:

https://bitbucket.org/lulinalex/mindstream/src/4cc7f0347a37bc378109843cce5b316044fa22d4/Examples/Scripts/CodeGeneration1/Testing.ms.dict?at=B284

// Testing.ms.dict

USES
 axiom_push.ms.dict
 macro.ms.dict
 params.ms.dict
 io.ms.dict
 EngineTypes.ms.dict
 Documentation.ms.dict
;

CONST cTests 'Tests:'

MACRO TestsFor
 ENGINE_WORD RIGHT LINK IN aName
  %REMARK 'aName ссылка на слово справа от TestsFor'
 %SUMMARY 'Определяет набор тестов для слова aName' ;

 axiom:PushSymbol VOID
 axiom:PushSymbol axiom:operator
 cTests aName |N Cat Ctx:Parser:PushSymbol
; // TestsFor

PRIVATE PROCEDURE DoRunTestsFor
 STRING IN aTestedWordName
 ENGINE_WORD IN aTestsHolder
 %SUMMARY 'Выполняет тесты для aTestsHolder' ;

 [ 'Testing: ' aTestedWordName ] strings:Cat Print
 aTestsHolder MembersIterator ==> (
  IN aTest
   %REMARK 'aTest - вложенный элемент aTestsHolder'
  if ( ( aTest %ST |N ) = ( NameOf Test ) ) then
   %REMARK '- фильтруем только тесты.'
  begin
   aTest |N Print
    %REMARK 'Печатаем имя теста'
   aTest DO
    %REMARK 'Запускаем тест'
  end // ( ( aTest %ST |N ) = 'Test' )
 )
 [ 'Testing end: ' aTestedWordName ] strings:Cat Print
 '------------------' Print
; // DoRunTestsFor

MACRO RunTests
 ENGINE_WORD RIGHT LINK IN aName
  %REMARK 'aName ссылка на слово справа от RunTests'
 %SUMMARY 'Выполняет тесты для aName' ;

 STRING VAR l_Name
 aName |N >>> l_Name
 STRING VAR l_TestsHolderName
 cTests l_Name Cat >>> l_TestsHolderName

 l_Name Ctx:Parser:PushString
 axiom:PushSymbol @
 l_TestsHolderName Ctx:Parser:PushSymbol
 axiom:PushSymbol DoRunTestsFor
; // RunTests


Ничего "космического".

Просто тесты "переехали" поближе к тому коду, который они тестируют.

Но! Это уже на самом деле - немало.

Это своего рода "инкапсуляция кода и контрактов к нему".

Код и тесты к нему - теперь находятся рядом.

Ведь тесты это своего рода "контракты к коду".

Работающие там, где не работает "статическая типизация".

Как когда-то правильно отметил Роман Янковский:

"В некотором смысле юнит-тесты решают абсолютно те же задачи, что и статическая типизация. Возможно, поэтому они наиболее прижились как раз там, где статической типизации нет.

Что такое статическая типизация? Это некое описание требований к коду, на соответствие котором компилятор будет наш код проверять. Что такое юнит-тесты? Это опять требования к коду, на соответствие которым код будет проверяться.

Если воспринимать юнит-тесты именно так, то исчезают все противоречия. С чего начинают разработку программы в языках со статической типизацией? С описания типов! С чего нужно начинать разработку программы в методологии TDD? С написания тестов! Так что это именно architecture first в обоих случаях.

Это выглядит не очевидно и разобщено, но во всем виноваты недостаточно развитые языковые средства, которыми мы пользуемся. Но тем не менее, в каком бы виде мы бы не описывали требования к входным и выходным данным некоторой функции, мы описываем ее ТИП и ничего более."

http://programmingmindstream.blogspot.ru/2013/11/tdd_28.html?showComment=1386063749826#c6337489882377572276

Вот собственно "эта идея" тут и развита.

Ну и "ничего нового в ней нет".

Похожие принципы описаны ещё у Барбары Лисков.

Я лишь "довёл идею до воплощения" - в одном отдельно взятом случае.

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

Если код может быть протестирован просто, то он должен быть протестирован.

А если тесты находятся рядом с кодом или инкапсулированы в него, то это - здорово.

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

Ну и если сделать "ещё один шаг", то из связки код-тесты-спецификация - можно увидеть исходные требования.

Ну и в конце хочу порекомендовать книгу:

"Использование абстракций и спецификаций при разработке программ

Барбара Лисков, Джон Гатэг"

"Описание

В книге американских специалистов излагаются основные способы создания спецификаций программ, повышающих эффективность разработки информационно-программного обеспечения. Значительное внимание уделено языку программирования CLU, позволяющему поддерживать различные типы абстракций, реализованных на языках ПЛ/1, Паскаль и Ада."

http://www.livelib.ru/book/1001241465

Это одна из моих любимых книг.

Комментариев нет:

Отправить комментарий