Peter Siebel - Practical Common Lisp

Здесь есть возможность читать онлайн «Peter Siebel - Practical Common Lisp» весь текст электронной книги совершенно бесплатно (целиком полную версию без сокращений). В некоторых случаях можно слушать аудио, скачать через торрент в формате fb2 и присутствует краткое содержание. Год выпуска: 2005, ISBN: 2005, Издательство: Apress, Жанр: Программирование, на английском языке. Описание произведения, (предисловие) а так же отзывы посетителей доступны на портале библиотеки ЛибКат.

Practical Common Lisp: краткое содержание, описание и аннотация

Предлагаем к чтению аннотацию, описание, краткое содержание или предисловие (зависит от того, что написал сам автор книги «Practical Common Lisp»). Если вы не нашли необходимую информацию о книге — напишите в комментариях, мы постараемся отыскать её.

Practical Common Lisp — читать онлайн бесплатно полную книгу (весь текст) целиком

Ниже представлен текст книги, разбитый по страницам. Система сохранения места последней прочитанной страницы, позволяет с удобством читать онлайн бесплатно книгу «Practical Common Lisp», без необходимости каждый раз заново искать на чём Вы остановились. Поставьте закладку, и сможете в любой момент перейти на страницу, на которой закончили чтение.

Тёмная тема
Сбросить

Интервал:

Закладка:

Сделать

(format t "~:[FAIL~;pass~] ... ~a: ~a~%" result *test-name* form)

With those changes, the test functions will still work but will produce the following output because *test-name*is never rebound:

CL-USER> (test-arithmetic)

pass ... NIL: (= (+ 1 2) 3)

pass ... NIL: (= (+ 1 2 3) 6)

pass ... NIL: (= (+ -1 -3) -4)

pass ... NIL: (= (* 2 2) 4)

pass ... NIL: (= (* 3 5) 15)

T

For the name to be reported properly, you need to change the two test functions.

(defun test-+ ()

(let ((*test-name* 'test-+))

(check

(= (+ 1 2) 3)

(= (+ 1 2 3) 6)

(= (+ -1 -3) -4))))

(defun test-* ()

(let ((*test-name* 'test-*))

(check

(= (* 2 2) 4)

(= (* 3 5) 15))))

Now the results are properly labeled.

CL-USER> (test-arithmetic)

pass ... TEST-+: (= (+ 1 2) 3)

pass ... TEST-+: (= (+ 1 2 3) 6)

pass ... TEST-+: (= (+ -1 -3) -4)

pass ... TEST-*: (= (* 2 2) 4)

pass ... TEST-*: (= (* 3 5) 15)

T

An Abstraction Emerges

In fixing the test functions, you've introduced several new bits of duplication. Not only does each function have to include the name of the function twice—once as the name in the DEFUN and once in the binding of *test-name*—but the same three-line code pattern is duplicated between the two functions. You could remove the duplication simply on the grounds that duplication is bad. But if you look more closely at the root cause of the duplication, you can learn an important lesson about how to use macros.

The reason both these functions start the same way is because they're both test functions. The duplication arises because, at the moment, test function is only half an abstraction. The abstraction exists in your mind, but in the code there's no way to express "this is a test function" other than to write code that follows a particular pattern.

Unfortunately, partial abstractions are a crummy tool for building software. Because a half abstraction is expressed in code by a manifestation of the pattern, you're guaranteed to have massive code duplication with all the normal bad consequences that implies for maintainability. More subtly, because the abstraction exists only in the minds of programmers, there's no mechanism to make sure different programmers (or even the same programmer working at different times) actually understand the abstraction the same way. To make a complete abstraction, you need a way to express "this is a test function" and have all the code required by the pattern be generated for you. In other words, you need a macro.

Because the pattern you're trying to capture is a DEFUN plus some boilerplate code, you need to write a macro that will expand into a DEFUN . You'll then use this macro, instead of a plain DEFUN to define test functions, so it makes sense to call it deftest.

(defmacro deftest (name parameters &body body)

`(defun ,name ,parameters

(let ((*test-name* ',name))

,@body)))

With this macro you can rewrite test-+as follows:

(deftest test-+ ()

(check

(= (+ 1 2) 3)

(= (+ 1 2 3) 6)

(= (+ -1 -3) -4)))

A Hierarchy of Tests

Now that you've established test functions as first-class citizens, the question might arise, should test-arithmeticbe a test function? As things stand, it doesn't really matter—if you did define it with deftest, its binding of *test-name*would be shadowed by the bindings in test-+and test-*before any results are reported.

But now imagine you've got thousands of test cases to organize. The first level of organization is provided by test functions such as test-+and test-*that directly call check. But with thousands of test cases, you'll likely need other levels of organization. Functions such as test-arithmeticcan group related test functions into test suites. Now suppose some low-level test functions are called from multiple test suites. It's not unheard of for a test case to pass in one context but fail in another. If that happens, you'll probably want to know more than just what low-level test function contains the test case.

If you define the test suite functions such as test-arithmeticwith deftestand make a small change to the *test-name*bookkeeping, you can have results reported with a "fully qualified" path to the test case, something like this:

pass ... (TEST-ARITHMETIC TEST-+): (= (+ 1 2) 3)

Because you've already abstracted the process of defining a test function, you can change the bookkeeping details without modifying the code of the test functions. [105] Though, again, if the test functions have been compiled, you'll have to recompile them after changing the macro. To make *test-name*hold a list of test function names instead of just the name of the most recently entered test function, you just need to change this binding form:

(let ((*test-name* ',name))

to the following:

(let ((*test-name* (append *test-name* (list ',name))))

Since APPEND returns a new list made up of the elements of its arguments, this version will bind *test-name*to a list containing the old contents of *test-name*with the new name tacked onto the end. [106] As you'll see in Chapter 12, APPEND ing to the end of a list isn't the most efficient way to build a list. But for now this is sufficient—as long as the test hierarchies aren't too deep, it should be fine. And if it becomes a problem, all you'll have to do is change the definition of deftest . When each test function returns, the old value of *test-name*will be restored.

Now you can redefine test-arithmeticwith deftestinstead of DEFUN .

(deftest test-arithmetic ()

(combine-results

(test-+)

(test-*)))

The results now show exactly how you got to each test expression.

CL-USER> (test-arithmetic)

pass ... (TEST-ARITHMETIC TEST-+): (= (+ 1 2) 3)

pass ... (TEST-ARITHMETIC TEST-+): (= (+ 1 2 3) 6)

pass ... (TEST-ARITHMETIC TEST-+): (= (+ -1 -3) -4)

pass ... (TEST-ARITHMETIC TEST-*): (= (* 2 2) 4)

pass ... (TEST-ARITHMETIC TEST-*): (= (* 3 5) 15)

T

As your test suite grows, you can add new layers of test functions; as long as they're defined with deftest, the results will be reported correctly. For instance, the following:

(deftest test-math ()

(test-arithmetic))

would generate these results:

CL-USER> (test-math)

pass ... (TEST-MATH TEST-ARITHMETIC TEST-+): (= (+ 1 2) 3)

pass ... (TEST-MATH TEST-ARITHMETIC TEST-+): (= (+ 1 2 3) 6)

pass ... (TEST-MATH TEST-ARITHMETIC TEST-+): (= (+ -1 -3) -4)

Читать дальше
Тёмная тема
Сбросить

Интервал:

Закладка:

Сделать

Похожие книги на «Practical Common Lisp»

Представляем Вашему вниманию похожие книги на «Practical Common Lisp» списком для выбора. Мы отобрали схожую по названию и смыслу литературу в надежде предоставить читателям больше вариантов отыскать новые, интересные, ещё непрочитанные произведения.


Отзывы о книге «Practical Common Lisp»

Обсуждение, отзывы о книге «Practical Common Lisp» и просто собственные мнения читателей. Оставьте ваши комментарии, напишите, что Вы думаете о произведении, его смысле или главных героях. Укажите что конкретно понравилось, а что нет, и почему Вы так считаете.

x