Автоматизирано тестване

„ Програмиране с Python“, ФМИ

Стефан Кънев & Николай Бачийски

11.04.2007г.

Disclaimer

Днес няма да си говорим за acceptance testing, quality assurance или нещо което се прави от "по-низшия" отдел във фирмата.

Митът

Проектът идва с готово задание, подробно задание. Прави се дизайн, с помоща на който работата се разбива на малки задачи. Те се извършват последователно, като не се налага да се връщате към някоя след като сте я приключили. Изискванията не се променят и не се налага добавянето на нова функционалност.

Митът v1.1

Щом съм написал един код, значи ми остава единствено да го разцъкам, да го пробвам в main метода/функцията и толкова. Няма да се променя така или иначе, а ако пък случайно ми се наложи да го пипам - аз съм го писал, знам го, няма как да допусна грешка. Най-много да го поразцъкам още малко.

Тежката действителност

Занятието


class Programmer(object):
    ...
    def makeChange(self, project, change):
        self.openAssociatedFiles(project, change)
        while True:
            self.attemptChange(change)
            project.start()
            result = self.clickAroundAndTest(project)
            project.stop()
            if result.sucessful(): break
        self.commitCode(project)
        self.hopeEverythingWentOK()

Идея

Това е автоматизирана задача. Всеки път повтаряме едно и също, отнема ни много време, уморява ни и има голям шанс да пропуснем нещо или да допуснем грешка. Защо вместо това не напишем малка програмка, която проверява дали кодът ни работи като хората? Така не просто ще имаме хубави критерии за кода, но и всеки друг ще може да проверява дали неговата промяна в модул А не е довела до грешка в нашия модул Б. Вместо да губим време да цъкаме всеки път, ще пускаме всички тестове автоматично и една команда, няма да въвеждаме никакви параметри и просто ще чакаме един от двата отгорова - "Всички тестове минаха усешно" или "Имаше грешка в тест Х".

Кодът който ще тестваме


class Interval(object):
    
    def __init__(self, left, right):
        self.left, self.right = left, right

    def leftOpen(self): return self.left == None
    def rightOpen(self): return self.right == None

    def containsNumber(self, number):
        if self.leftOpen() and self.rightOpen(): return true
        if self.leftOpen(): return number <= self.right
        if self.rightOpen(): return self.left <= number
        return self.left < number < self.right

    def __max(self, a, b): max(a, b) if not None in [a, b] else None
    def __min(self, a, b): min(a, b) if not None in [a, b] else None

    def insersect(self, other):
        left = self.__max(self.left, other.left)
        right = self.__max(self.right, other.right)
        return Interval(left, right)
    

Идеята...


    class IntervalTest:
        def testContainsNumber(self):
            interval = Interval(None, 0)
            test("interval съдържа -3")
            test("interval съдържа 0")
            test("interval не съдържа 9")
            test("interval.leftOpen() е истина")
            test("interval.rightOpen() е лъжа")

        def testIntersects(self):
            test("сечението на [0, 10] със [5, None] е [5, 10]")
            test("сечението на [None, 0] със [None, 42] е [None, 0]")
            test("сечението на [None, 20] със [-20, None] е [-20, 20]")

    

...реализацията...

    class IntervalTest(unittest.TestCase):
        def testContainsNumber(self):
            interval = Interval(None, 0)
            self.assertTrue(interval.containsNumber(-3))
            self.assertTrue(interval.containsNumber(0))
            self.failIf(interval.containsNumber(9))
            self.assertTrue(interval.leftOpen())
            self.failIf(interval.rightOpen())

        def testIntersects(self):
            self.assertEquals(
                Interval(5, 10),
                Interval(0, 10).intersect(Interval(5, None)))
            self.assertEquals(
                Interval(None, 0),
                Interval(None, 42).intersect(Interval(None, 0)))
            self.assertEquals(
                Interval(-20, 20),
                Interval(None, 20).intersect(Interval(-20, None)))

    if __name__ == "__main__":
        unittest.main()
    

...и резултата

.F
======================================================================
FAIL: testIntersects (__main__.GoodieListTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./interval_test.py", line 16, in testCompact
    self.assertEquals(
        Interval(None, 0),
        Interval(None, 42).intersects(Interval(None, 0)))
AssertionError: Interval(None, 0) != Interval(0, 0)

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (failures=1)

Структура на тестовете

Структура на тестовете в Python

Видове тестове

За какво ни помагат тестовете

Още въпроси?