Сайт Игоря Кононученко   Статьи

Continuous Integration в django-проекте

4 июня 2008

Предпосылки

Проект относительно небольшой с командой из 4 разработчиков. Хотелось процесс разработки максимально приблизить к описанному в книге Continuous Integration: Improving Software Quality and Reducing Risk Были поставлены требования:

  • юнит-тестинг
  • проверка кода на соответствие стандартам
  • автоматизированный подъем проекта на сервере для тестирования
  • быстрый прогон тестов (до 10 секунд)

Юнит-тестинг

Доктест сразу отпал (с тдд маловато общего). В юнит-тестинге сразу столкнулся со сложностью:

  • The models.py file. The test runner looks for any subclass of unittest.TestCase in this module.
  • A file called tests.py in the application directory — i.e., the directory that holds models.py. Again, the test runner looks for any subclass of unittest.TestCase in this module
отсюда

Стандартный механизм запуска тестов требует, чтоб в тесты были плотно интегрированны в рабочий проект. С чем лично я не согласен. Считаю, что тесты должны быть отделены от продакшн кода и должны лежать в отдельной директории. Также не устраивало, что джанго создает MySql тестовую базу данных, что сильнейшим образом замедляет выполнение тестов.

С базой данной проблема решалась путем замены MySql на Sqlite (: memory: — она в оперативной памяти) во время тестинга (решение отсюда).

Для того чтоб тесты лежали в отдельной директории, в этой же директории был создан файлик suite.py, в котором приблизительно в таком виде все происходит

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
suite = unittest.TestSuite()


tests = {
            "engine.testParser": "TestParser",
            "engine.testModuleFinder": "TestModuleFinder",
            "engine.testModuleRegistrator": "TestModuleRegistrator",
            "test_code_rules": "TestCodeRules", # тут запускается pep8
            "setup_database": "TestSetupDatabase", тут выполняется дамп и перезаписывается бд
        }
suite = registerTests(tests) # функция, которая занимается импортом тестов


if __name__ == '__main__':
    runner = getRunner() # для интеграции с тимсити испльзуется свой руннер
    runner.verbosity = 2
    runner.run(suite)
Вот две функции из кода выше:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import unittest


def registerTests(tests):
   
    suite = unittest.TestSuite()
   
    for key, value in tests.items():
        module =__import__('yourproject.tests.%s' % key, globals(), locals(), [value], -1)
        suite.addTest(getattr(module, value).suite())
   
    return suite


#для того чтоб локально прогонять тесты юзаем обычный текстест раннер
def getRunner():
    runner = unittest.TextTestRunner()
    #if teamcity integrated then use runner from team city
    try:
        from teamcity import underTeamcity
        from teamcity.unittestpy import TeamcityTestRunner
        if underTeamcity():
            runner = TeamcityTestRunner()   
    except ImportError:
        pass
    return runner

Для моков используем PyMock - довольно таки неудобная штука, в плане своих мало информативных эрроров (иннапроприейт экшн). Вроде есть либы получше.

Проверка кода на соответствие стандартам

Для проверки кода используется утилита pep8

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class TestCodeRules(testcasebase.TestCaseWrapper):
    def testRules(self):
        sys.argv[1:] = ['--filename=*.py', '--ignore=E202,E702,W291,E301,W292', yourproject.__path__[0]]  # сделал некоторые поблажки себе
        buf = StringIO()       
        sys.stdout = buf #replace to our buffer
        pep8.run()          
        sys.stdout = sys.__stdout__ #return default command line
        result = buf.getvalue();
       
        self.assertEqual("", result, "Code messages should be empty but was:\n" + result)
Внедрялось с некоторым недовольством со стороны разработчиков. Но довольно таки полезная вещь для поддержания порядка в коде.

Билд-сервер

Можно сказать, что какой там билд сервер может быть в питоне и что там билдить. Я не знаю как его назвать иначе. В моем случае это сервер с Windows, на котором стоит TeamCity. Очень удобная штука в работе, с отличным веб интерфейсом. Конфигурить очень просто и быстро. Позволяет делать Pre-tested Commit (в моем случае не очень хорошо работало). Он периодично проверяет SVN (мы его используем) на наличие изменений и запускает билд скрипт- в моем случае это обычный build.bat файл, который делает апдейт из свн, выполняет тесты и рестартует Apache, который у нас на сервере. С этого момента проект можно открывать и тестировать на билд сервере. Участники проекта, в зависимости от конфигурации оповещений узнают об успешности или провале билда.

Реалии проекта и планы

Для команды это первый проект с использованием Contineous Integration. Как и первый серьезный django-проект. Дедлайн как всегда очень рядом. Вероятно, не все звенья процесса разработки близки к идеалу, но процесс есть и работает. В планах внедрить Selenium тесты, cделать автоматизированный деплой проекта в продакшн.

Прагматичное юнит-тестирование
Linq to SQL и DDD
Ctrl
Template из Prototype.js на C# 3.0
Python yield — простым языком