Добрый день.
Модуль unittest входит в стандартную библиотеку Python и служит базовым инструментом для организации регрессионных unit-тестов. Рассмотрим небольшой пример. Файл account.py содержит класс BankAccount, предоставляющий средства для работы с банковским счетом: создание счета с начислением бонуса, добавление и снятие денег, начисление процентов.
class BankAccount: def __init__ (self, bonus): self.balance = bonus def deposit (self, amount): self.balance += amount def withdraw (self, amount): if self.balance > amount: self.balance -= amount else: self.balance = 0 def interest (self, rate): self.balance = self.balance + (self.balance * rate)/100 def get(self): return self.balance if __name__ == '__main__': account = BankAccount(30) account.deposit(50) account.withdraw(10) account.interest(8.5) balance = account.get() print balance
Запуск account.py из командной строки cлужит примером работы с BankAccount (результат = 75.95). Подготовим тесты для трех методов: deposit, withdraw и interest. Разместим их в модуле test_account.py:
from account import BankAccount import unittest class TestBankAccount (unittest.TestCase): def setUp(self): self.account = BankAccount (100) def testBankAccountDeposit(self): test_balance = 170 self.account.deposit (70) self.assertEqual(self.account.balance, test_balance) def testBankAccountWithdraw(self): test_balance = 30 self.account.withdraw (70) self.assertEqual(self.account.balance, test_balance) self.account.withdraw (270) self.assertEqual(self.account.balance, 0) def testBankAccountInterest(self): test_balance = 108.5 self.account.interest (8.5) self.assertEqual(self.account.balance, test_balance) if __name__ == "__main__": unittest.main()
Метод setUp() – служебный. Он вызывается перед запуском каждого теста и подготавливает среду выполнения. В нашем случае метод setUp() создает банковский аккаунт и помещает на счет 100 единиц. Имена остальных методов начинаются с “test” (необходимое условие для нахождения тестов в коде модуля). Запуск test_account.py:
[capt@rh Work]# python test_account.py -v testBankAccountDeposit (__main__.TestBankAccount) ... ok testBankAccountInterest (__main__.TestBankAccount) ... ok testBankAccountWithdraw (__main__.TestBankAccount) ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.005s OK
Приложение, достойное модульного тестирования, как правило содержит больше одного модуля. Соответственно, unittest предоставляет возможности для централизованного управления всеми тестами. Создадим агрегатор, который будет запускать тесты из класса TestBankAccount и тесты нахождения палиндромов.
Прежде всего, внесем изменения в test_account.py, добавив в него создание комплекта тестов класса BankAccount:
from account import BankAccount import unittest class TestBankAccount (unittest.TestCase): def setUp(self): self.account = BankAccount (100) def testBankAccountDeposit(self): test_balance = 170 self.account.deposit (70) self.assertEqual(self.account.balance, test_balance) def testBankAccountWithdraw(self): test_balance = 30 self.account.withdraw (70) self.assertEqual(self.account.balance, test_balance) self.account.withdraw (270) self.assertEqual(self.account.balance, 0) def testBankAccountInterest(self): test_balance = 108.5 self.account.interest (8.5) self.assertEqual(self.account.balance, test_balance) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestBankAccount)) return suite
Далее, подготовим служебный файл test_transformation с результатами doctest-проверки наличия палиндромов:
>>> from transformation import is_palindrome >>> is_palindrome("hello hhh again") hhh >>> is_palindrome("") Traceback (most recent call last): ... ValueError: Empty String!
Создадим скрипт-агрегатор test_aggregator.py:
import unittest import test_account import doctest import transformation suiteAccount = test_account.suite() suitePalindrome = unittest.TestSuite() suitePalindrome.addTest(doctest.DocFileSuite("test_transformation")) suite = unittest.TestSuite() suite.addTest(suiteAccount) suite.addTest(suitePalindrome) unittest.TextTestRunner(verbosity=2).run(suite)
Запуск test_aggregator.py:
[capt@rh Work]# python test_aggregator.py testBankAccountDeposit (test_account.TestBankAccount) ... ok testBankAccountInterest (test_account.TestBankAccount) ... ok testBankAccountWithdraw (test_account.TestBankAccount) ... ok Doctest: test_transformation ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.005s OK
Убедимся, что при изменении алгоритма метода withdraw наш тест закончится неудачей. Изменим этот метод в модуле account.py:
def withdraw (self, amount): self.balance -= amount
Запуск test_aggregator.py:
[capt@rh Work]# python test_aggregator.py testBankAccountDeposit (test_account.TestBankAccount) ... ok testBankAccountInterest (test_account.TestBankAccount) ... ok testBankAccountWithdraw (test_account.TestBankAccount) ... FAIL Doctest: test_transformation ... ok ====================================================================== FAIL: testBankAccountWithdraw (test_account.TestBankAccount) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/Work/test_account.py", line 19, in testBankAccountWithdraw self.assertEqual(self.account.balance, 0) AssertionError: -240 != 0 ---------------------------------------------------------------------- Ran 4 tests in 0.006s FAILED (failures=1)
Результат соответствует ожидаемому.
Дополнительную информацию о модуле unittest можно почерпнуть в документации Python.
Успехов в модульных тестах. Оставайтесь с нами.
Что такое качество программного обеспечения и как его улучшить: теория и практика, задачи и решения, подводные камни и обходные пути.
Pingback : OpenQuality.ru | Ступень Мартина, или двойники в Python | April 21, 2009
[…] возьмем файл account.py, содержащий класс BankAccount, из нашего примера c unittest. Далее создадим файл test.py, в котором […]
Автор комментария : Ochir | June 23, 2009
Немного из своего опыта.
Очень удобна структура построения тестового фреймворка на базе чистого unittest, если необходимо иметь четкую структуру тестов, основанных на базовом классе.
Но гораздо удобнее использовать nosetests (надстройка для unitttest) или py.test (часть библиотеки py.lib). Оба обладают схожими возможностями и позволяет эффективно управлять тестами.
[Ответить]
Автор комментария : Капитан Аляска | June 23, 2009
Ochir, спасибо за ценный комментарий! Да, в сторону nose я смотрел, но пока функциональности unittest хватало. py.test – пока нет. Много их, framework’ов :)
[Ответить]
Автор комментария : Ochir | June 24, 2009
а чего в nose не хватает?
это же надстройка вроде, он все возможности unittest в себя включает и можно писать обычные unittest, но потом отлаживать с помощью nosetests с опциями.
Или если unittest совсем не нравится, можно сразу py.test пробовать.
[Ответить]
Автор комментария : Капитан Аляска | June 24, 2009
Я сформулировал это для себя несколько иначе: не увидел в nose чего-нибудь такого, чтобы захотелось его использовать. Функциональности unittest для моих задач было достаточно. А дальше – время покажет.
[Ответить]
Автор комментария : Ochir | June 24, 2009
согласен, это дело удобства и привычки.
[Ответить]
Автор комментария : wessenhizer | May 13, 2011
Статья понравилась. Я до этого читал другие статьи и не мог понять что за тесты такие и как их делать, а тут обратил внимание, что меня привлекло в этой статье, оказалось подсветка синтаксиса! Я был удивлён своему открытию, я просто читал код и не замечал подсветку, и код из за этого выглядел понятнее. Питонер я начинающий так что возьму на заметку.
[Ответить]
Автор комментария : Капитан | May 23, 2011
Wessenhizer, спасибо на добром слове.
[Ответить]