То, что было реализовано в главе 9 книги назвать фреймворком както язык не поворачивается.. Фреймворк в 16 строчек кода (если не учитывать комментарии) - это смешно) Человеку программирующему на java, C#, C++ трудно понять, что там такого супермощного можно закодировать в 16-ти строчках кода.
Итак взяв за основу эти 16 строк кода я прикрутил проверку времени выполнения, используемой памяти, проверку не было ли выброшено исключение во время работы алгоритма. Вообще по возможностям старался приблизится к решению на C# которое описано в посте Online Judge unit testing.
Получился такой код (кто не знаком с лиспом - пропускайте):
; файл unittesting.lisp
(defvar *test-name* nil)
(defvar *test-time-limit* 2)
(defvar *test-memory-limit* 160)
(defvar *data-files-dir* nil)
(define-condition assert-error (error)
((text :initarg :text :reader text)))
(defmacro assert-equal (expected real &optional prefix)
(with-gensyms (nil e r msg prefValue)
`(let ((,e ,expected) (,r ,real))
(if (equal ,e ,r)
t
(let* ((,msg (format nil "Expected [~a], but was [~a]" ,e ,r))
(,prefValue ,prefix))
(error 'assert-error :text
(if ,prefValue (concatenate 'string "[" ,prefValue "] " ,msg) ,msg)))
))
))
(defmacro time-memory-result ((&body body))
(with-gensyms (nil start res end time memory)
`(let* ((,start (get-internal-real-time))
(,res ,body)
(,end (get-internal-real-time))
(,time (float (/ (- ,end ,start) internal-time-units-per-second)))
(,memory (float (/ (sys::%room) (* 1024 1024)))))
(list ,time ,memory ,res))
)
)
(defmacro deftest (name parameters &body body)
"Define a test function. Within a test function we can call
other test functions or use 'check' to run individual test
cases."
`(defun ,name ,parameters
(let ((*test-name* (append *test-name* (list ',name))))
,@body)))
(defun validate (time memory res)
(let ((violations nil))
(progn
(if (> memory *test-memory-limit*)
(setf violations (cons
(format nil "[Memory limit exceeded] Expected less than ~aMB, but was ~aMB"
*test-memory-limit* memory) violations)))
(if (> time *test-time-limit*)
(setf violations (cons
(format nil "[Time limit exceeded] Expected less than ~as, but was ~as"
*test-time-limit* time) violations)))
(if (not res)
(setf violations (cons "Return NIL" violations)))
violations)
))
(defmacro check (&body forms)
"Run each expression in 'forms' as a test case."
(with-gensyms (nil time-memory-res time memory res violations ae e)
`(combine-results
,@(loop for f in forms collect
`(handler-case
(let* ((,time-memory-res (time-memory-result ,f))
(,time (nth 0 ,time-memory-res))
(,memory (nth 1 ,time-memory-res))
(,res (nth 2 ,time-memory-res))
(,violations (validate ,time ,memory ,res)))
(report-result ,violations ',f))
(assert-error (,ae) (report-result (list (text ,ae)) ',f))
(error (,e) (report-result
(list (format nil "Error during execution: ~a" ,e)) ',f)))))
))
(defmacro combine-results (&body forms)
"Combine the results (as booleans) of evaluating 'forms' in order."
(with-gensyms (nil allTestsSuccess)
`(let ((,allTestsSuccess t))
,@(loop for f in forms collect `(unless ,f (setf ,allTestsSuccess nil)))
,allTestsSuccess)))
(defun report-result (violations form)
"Report the results of a single test case. Called by 'check'."
(if (= (length violations) 0)
(format t "pass ... ~a: ~a~%" *test-name* form)
(format t "FAIL ... ~a: ~a~% reason: ~{~A~^, ~} ~%"
*test-name* form violations))
(= (length violations) 0))
(defun assert-eq-streams (in1 in2)
(let ((lineNum 0))
(progn
(loop
for line1 = (read-line in1 nil)
for line2 = (read-line in2 nil)
while (or line1 line2)
do
(progn
(incf lineNum)
(assert-equal line1 line2 (format nil "line ~a" lineNum))))
t)
))
(defun relativep (path)
(let ((dir (pathname-directory path)))
(or (= 0 (length dir)) (equal :relative (nth 0 dir)))
))
(defun make-absolute-path (path)
(if (relativep path) (merge-pathnames path *data-files-dir*) path)
)
(defun file-io-test (algorithm inPath outPath)
(progn
(setf inPath (make-absolute-path inPath))
(setf outPath (make-absolute-path outPath))
(let ((res (with-open-file (in inPath
:if-does-not-exist nil)
(with-output-to-string (out)
(funcall algorithm in out))
)))
(with-input-from-string (result res)
(with-open-file (expected outPath
:if-does-not-exist nil)
(assert-eq-streams expected result)
))
)
))
Как этим пользоваться:
(load "F:\\jFiles\\workspaces\\tests\\SPOJ\\ARITH\\ARITH.lisp") ; загружаем файл с алгоритмом
; алгоритм - реализован в функции exec которая принимает 2 потока - входной и выходной.
(load "F:\\jFiles\\workspaces\\tests\\SPOJ\\unittesting.lisp") ; загружаем библиотеку (код выше)
(setf *test-time-limit* 5) ; устанавливаем ограничение по времени в секундах для каждого теста
(setf *test-memory-limit* 16) ; устанавливаем ограничение по памяти
(setf *data-files-dir* #p"F:\\jFiles\\workspaces\\tests\\SPOJ\\ARITH\\data\\") ; устанавливаем абсолютный
; путь к примерам входных и выходных данных
(deftest tests () ; определение тестов (каждая строчка внутри check - один тест)
(check
(file-io-test #'exec #p"in1.txt" #p"out1.txt")
(file-io-test #'exec #p"in2.txt" #p"out2.txt")
(file-io-test #'exec #p"in3.txt" #p"out3.txt")
(file-io-test #'exec #p"in4.txt" #p"out4.txt")
(file-io-test #'exec #p"in5.txt" #p"out5.txt")
(file-io-test #'exec #p"in6.txt" #p"out6.txt")
))
(tests) ; запуск тестов
Пример возможного отчета по тестам:
pass ... (TESTS): (FILE-IO-TEST #'EXEC in1.txt out1.txt)
pass ... (TESTS): (FILE-IO-TEST #'EXEC in2.txt out2.txt)
pass ... (TESTS): (FILE-IO-TEST #'EXEC in3.txt out3.txt)
FAIL ... (TESTS): (FILE-IO-TEST #'EXEC in4.txt out4.txt)
reason: [line 18] Expected [ ---------], but was [---------]
FAIL ... (TESTS): (FILE-IO-TEST #'EXEC in5.txt out5.txt)
reason:
Error during execution:
WRITE-LINE: keyword arguments in (#
FAIL ... (TESTS): (FILE-IO-TEST #'EXEC in6.txt out6.txt)
reason: [Time limit exceeded] Expected less than 5s, but was 6.0670038s, [Memory limit exceeded] Expected less than 16MB, but was 99.79423MB
Первые 3 теста прошли успешно.
Тест 4 свалился по причине того что в строке 18 вывод алгортма перестал совпадать с ожидаемым выводом.
Тест 5 свалился из-за ошибки в рантайме.
Тест 6 провалился по 2 причинам: превышены лимиты времени и памяти.
Итак, всего в 127 строчках кода удалось выразить все то же что и на C# используя суперфрейморк тестирования и вижуал студию для запуска тестов :) Как по мне - хороший результат)
Комментариев нет:
Отправить комментарий