Skip to content

Instantly share code, notes, and snippets.

@skanev
Created August 31, 2009 20:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skanev/178676 to your computer and use it in GitHub Desktop.
Save skanev/178676 to your computer and use it in GitHub Desktop.
sub opening_slide {
$_ = shift;
my ($title, $date, $intro) = (/^(.*?)\n(.*?)\n(.*)$/s);
$intro = html_escape(trim($intro));
$intro =~ s#\n#<br />\n#g;
$intro =~ s# # &nbsp;#g;
html_head($title, $date, $intro);
}
sub format_slide {
$_ = shift;
s/\{\{\{(\w+)?\n(.*?)\n\}\}\}/'{{{'.codenote($2, $1).'}}}'/smge;
s/"(.+?)"/&#8222;$1&#8220;/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/--/&mdash;/g;
s/\.\.\./&hellip;/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});
}
Автоматизирано (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()
}}}
== Идея ==
— Добре де... хващам се, че постоянно правя едно и също нещо като робот. Понеже е досадно, лесно ще забравя нещо. Пък и само ми губи времето. Човешката цивилизация не реши ли тоя вид проблеми с някакви машини? Май се казваха компютри?
&nbsp;
— Защо просто не си напишеш програма, която да го прави вместо теб?
== На хартия (или проектор) ==
* ((За всичко съмнително ще пишем ***сценарий***, който да "цъка".))
* ((Всеки сценарий ще изпълнява кода и ще прави няколко ***твърдения*** за резултатите.))
* ((Сценариите ще бъдат обединени в ***групи***.))
* ((Пускате всички тестове с едно бутонче.))
* ((Резултатът е "Всичко мина успешно" или "Твърдения 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/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment