Created
August 31, 2009 20:03
-
-
Save skanev/178676 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sub opening_slide { | |
$_ = shift; | |
my ($title, $date, $intro) = (/^(.*?)\n(.*?)\n(.*)$/s); | |
$intro = html_escape(trim($intro)); | |
$intro =~ s#\n#<br />\n#g; | |
$intro =~ s# # #g; | |
html_head($title, $date, $intro); | |
} | |
sub format_slide { | |
$_ = shift; | |
s/\{\{\{(\w+)?\n(.*?)\n\}\}\}/'{{{'.codenote($2, $1).'}}}'/smge; | |
s/"(.+?)"/„$1“/g; | |
s#^==\s+(.*?)\s+==(l+)?(.*)\z#'<div class="slide'.($2 ? " ".("long" x length($2)) : "")."\">\n <h1>$1</h1>$3\n</div>"#se; | |
s#\*\*\*(.*?)\*\*\*#<strong>$1</strong>#g; | |
s#`(.*?)`#<code>$1</code>#sg; | |
s#\[\[(.+?) (\S+?:\S+?)\]\]#<a href="$2">$1</a>#g; | |
s#(?<!")(http://\S+)#<a href="$1">$1</a>#g; | |
s#((^ \*.*?\n)+)# <ul>\n$1 </ul>\n#mg; | |
s#((^ \#.*?\n)+)# <ol>\n$1 </ol>\n#mg; | |
s/^ [\#*]\s*(.*)$/ <li>$1<\/li>/mg; | |
s#<li>\(\((.*?)\)\)</li>#<li class="incremental">$1</li>#g; | |
s#((^[^ <{\n].+?\n)+)#' <p>'.trim($1)."</p>\n"#mge; | |
s#<p>\(\((.*?)\)\)</p>#<p class="incremental">$1</p>#mg; | |
s/\n+/\n/g; | |
s/--/—/g; | |
s/\.\.\./…/g; | |
s/\{\{\{(\d+)\}\}\}/format_codenote($1)/ge; | |
$_; | |
} | |
# === CODE NOTES ========================================================== | |
our @codenotes; | |
sub codenote { | |
push @codenotes, {code => $_[0], format => $_[1]}; | |
$#codenotes; | |
} | |
sub format_codenote { | |
my ($note, $formats) = $codenotes[shift]; | |
$formats = { | |
bare => sub { $_[0] }, | |
code => sub { " <pre><code>".html_escape($_[0])."</code></pre>" }, | |
python => sub { " <pre class=\"prettyprint\">\n$_[0]\n</pre>" }, | |
with_output => sub { join "\n", $formats->{python}->($_[0]), $formats->{code}->(python_output($_[0])) }, | |
}; | |
no warnings; | |
($formats->{$note->{format}} || $formats->{python})->($note->{code}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Автоматизирано (unit) тестване | |
22.04.2009 | |
self.assertEquals(set(lecturers), | |
set(['Стефан Кънев', 'Николай Бачийски', 'Точо Точев', 'Димитър Димитров'])) | |
== Disclaimer == | |
Днес няма да си говорим за acceptance testing, quality assurance или нещо, което се прави от "по-низшия" отдел във фирмата. Всичко тук е дело на програмиста. | |
== Митът == | |
Проектът идва с готово, подробно задание. Прави се дизайн. С него работата се разбива на малки задачи. Те се извършват последователно. За всяка от тях пишете кода и приключвате. Изискванията не се променят, нито се добавя нова функционалност. | |
== Митът v1.1 == | |
Щом съм написал един код, значи ми остава единствено да го разцъкам - няколко `print`-а, малко пробване в `main` метода/функцията и толкова. Така или иначе няма да се променя. А ако (не дай си боже) това се случи - аз съм го писал, знам го, няма как да допусна грешка. Най-много да го поразцъкам още малко. | |
== Тежката действителност == | |
* ((Заданията ***винаги*** се променят.)) | |
* ((Често се налага един код да се преработва.)) | |
* ((Писането на код е сложна задача - допускат се грешки.)) | |
* ((Програмистите (освен Мило) са хора - допускат грешки (освен Мило).)) | |
* ((Промяната на модул в единия край на системата като нищо може да счупи модул в другия край на системата. )) | |
== Традиционния подход == | |
{{{python | |
class Programmer(object): | |
# ... | |
def implement_a_change(self, project, change): | |
files = self.open_related_files(project, change) | |
while True: | |
self.attempt_change(change, files) | |
project.run() | |
result = self.click_around_and_test(project) | |
project.stop() | |
if result.successful(): break | |
self.commit_code(project, files) | |
self.hope_everything_went_ok() | |
}}} | |
== Идея == | |
— Добре де... хващам се, че постоянно правя едно и също нещо като робот. Понеже е досадно, лесно ще забравя нещо. Пък и само ми губи времето. Човешката цивилизация не реши ли тоя вид проблеми с някакви машини? Май се казваха компютри? | |
| |
— Защо просто не си напишеш програма, която да го прави вместо теб? | |
== На хартия (или проектор) == | |
* ((За всичко съмнително ще пишем ***сценарий***, който да "цъка".)) | |
* ((Всеки сценарий ще изпълнява кода и ще прави няколко ***твърдения*** за резултатите.)) | |
* ((Сценариите ще бъдат обединени в ***групи***.)) | |
* ((Пускате всички тестове с едно бутонче.)) | |
* ((Резултатът е "Всичко мина успешно" или "Твърдения X, Y и Z в сценарии A, B и C се оказаха неверни".)) | |
== Кодът, който ще тестваме ==ll | |
{{{python | |
class Interval(object): | |
def __init__(self, left, right): self.left, self.right = left, right | |
def __repr__(self): return "Interval({0}, {1})".format(self.left, self.right) | |
def __eq__(self, other): | |
return isinstance(other, Interval) and \ | |
(self.left, self.right) == (other.left, other.right) | |
def left_open(self): return self.left == None | |
def right_open(self): return self.right == None | |
def contains_number(self, number): | |
if self.left_open() and self.right_open(): return true | |
if self.left_open(): return number <= self.right | |
if self.right_open(): return self.left <= number | |
return self.left < number < self.right | |
def intersect(self, other): | |
extr = lambda a, b, func: func(a, b) if not None in (a, b) else a or b | |
return Interval( | |
extr(self.left, other.left, max), | |
extr(self.right, other.right, min)) | |
__and__ = intersect | |
}}} | |
== Идеята... ==l | |
{{{python | |
class IntervalTest: | |
def test_contains_number(self): | |
interval = Interval(None, 0) | |
твърдя_че("interval съдържа -3") | |
твърдя_че("interval съдържа 0") | |
твърдя_че("interval не съдържа 9") | |
твърдя_че("interval.left_open() е истина") | |
твърдя_че("interval.right_open() е лъжа") | |
def test_intersects(self): | |
твърдя_че("сечението на [0, 10] с [5, None] е [5, 10]") | |
твърдя_че("сечението на [None, 0] с [None, 42] е [None, 0]") | |
твърдя_че("сечението на [None, 20] с [-20, None] е [-20, 20]") | |
твърдя_че("сечението на [None, 0] с [-10, None] е [-10, 0]") | |
}}} | |
== ...реализацията... ==l | |
{{{python | |
class IntervalTest(unittest.TestCase): | |
def test_contains_number(self): | |
interval = Interval(None, 0) | |
self.assertTrue(interval.contains_number(-3)) | |
self.assertTrue(interval.contains_number(0)) | |
self.failIf(interval.contains_number(9)) | |
self.assertTrue(interval.left_open()) | |
self.failIf(interval.right_open()) | |
def test_intersects(self): | |
self.assertEquals( | |
Interval(5, 10), Interval(0, 10) & Interval(5, None)) | |
self.assertEquals( | |
Interval(None, 0), Interval(None, 42) & Interval(None, 0)) | |
self.assertEquals( | |
Interval(-20, 20), Interval(None, 20) & Interval(-20, None)) | |
self.assertEquals( | |
Interval(-10, 0), Interval(None, 0) & Interval(-10, None)) | |
if __name__ == "__main__": | |
unittest.main() | |
}}} | |
== ...и резултата == | |
{{{ | |
.F | |
====================================================================== | |
FAIL: test_intersects (__main__.IntervalTest) | |
---------------------------------------------------------------------- | |
Traceback (most recent call last): | |
File "<stdin>", line 52, in test_intersects | |
AssertionError: Interval(-10, 0) != Interval(-10, None) | |
---------------------------------------------------------------------- | |
Ran 2 tests in 0.001s | |
FAILED (failures=1) | |
}}} | |
== bulgarian: (english, python) ==l | |
{{{python | |
vocabulary = { | |
"група": ("test case", unittest.TestCase), | |
"сценарий": ("test method", | |
[_ for _ in dir(YourTestCase) if _.startswith("test")]), | |
"твърдение": ("assertion", | |
[_ for _ in dir(unittest.TestCase) if re.match("assert|fail", _)]) | |
} | |
}}} | |
***Важно.*** Не бъркайте ключовата дума `assert` с методите за твърдения в тестовете. Първото служи да прекратите програмата ако изпадне в невалидно състояние. Второто е част от библиотеката за тестове. | |
== Твърдения в unittest.TestCase ==l | |
Всички методи имат опционален последен аргумент `msg` - текстово съобщение, което ще се покаже ако теста пропадне. | |
* ((`self.assertTrue(expr)` - още `assert_` и `failUnless`)) | |
* ((`self.assertFalse(expr)` - още `failIf`)) | |
* ((`self.assertEqual(expected, actual)` - още `assertEquals` и `failUnlessEqual`)) | |
* ((`self.assertAlmostEqual(expected, actual, places=7)` - още `assertAlmostEquals` и `failUnlessAlmostEqual`)) | |
* ((`self.assertNotAlmostEqual(expected, actual, places=7)` - още `assertNotAlmostEquals` и `failIfAlmostEqual`)) | |
* ((`self.assertRaises(self, excClass, callable, *args, **kwargs)` - още `failUnlessRaises`)) | |
== Видове тестове == | |
* ((***Unit tests*** - проверяват дали дадено парче код/клас работи правилно в изолация)) | |
* ((***Integration tests*** - проверяват дали няколко модула си общуват правилно)) | |
* ((***Functional tests*** - проверяват дали крайната функционалност е както се очаква)) | |
== За какво ни помагат тестовете == | |
* ((Откриват грешки по-рано)) | |
* ((Позволяват ни уверено да правим промени в системата)) | |
* ((Дават сигурност на клиенти, шефове и програмисти)) | |
* ((Представляват пример как се работи с кода)) | |
* ((Помага разделянето на интерфейс от имплементация)) | |
* ((Служат като документация и спецификация)) | |
== За какво не служат тестовете == | |
* ((***Не доказват***, че приложението работи)) | |
* ((Не са Quality Assurance)) | |
* ((Не са benchmark)) | |
== Още речник == | |
* ((black-box тестове)) | |
* ((glass-box тестове)) | |
* ((fixture (`setUp` и `tearDown`))) | |
== Test-Driven Development == | |
# Добави тест | |
# Пусни всички тестове и виж, че новия се чупи | |
# Напиши код | |
# Пусни тестовете и виж че минават успешно | |
# Подобри кода (refactor) | |
# Повтаряй | |
== Behaviour Driven Development == | |
* rspec | |
* The RSpec Book | |
== Документация ==l | |
{{{python | |
class Foo: | |
""" | |
Sample Foo class | |
""" | |
def foo(self): | |
""" | |
Sample foo method | |
Returns: 2 | |
""" | |
return 2 | |
}}} | |
== Документацията като тестове ==l | |
{{{python | |
def add(a, b): | |
""" | |
Adds the two arguments. | |
>>> add(1, 3) | |
4 | |
>>> add(1, '') | |
Traceback (most recent call last): | |
... | |
TypeError: unsupported operand type(s) for +: 'int' and 'str' | |
""" | |
return a + b | |
if __name__ == '__main__': | |
import doctest | |
doctest.testmod() | |
}}} | |
== Шепа съвети == | |
* ((***Не пишете тестове за абсолютно всичко***. Тестовете са като предпазна мрежа, а твърде много предпазни мрежи само пречат.)) | |
* ((Не тествайте елементарен код.)) | |
* ((Не използвайте произволни тестови данни.)) | |
* ((Успеха на тестовете не трябва да зависи от реда им.)) | |
* ((Тествайте гранични случаи!)) | |
* ((Не правете тестовете зависими един от друг.)) | |
== Още въпроси? == | |
* Страница на курса: http://fmi.py-bg.net/ | |
* Форуми на курса: http://fmi.py-bg.net/topics/ | |
* http://extremeprogramming.org/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
http://fmi.py-bg.net/slides/08-09/12-unittest.html |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment