Categories

Checkio.ORG

Subscribe to Posts

Email:

  • 10Jun

    В Django есть такая удобная вещь для написания тестов — это fixtures. Удобство состоит в том, что ваши тесты могут входить в уже заполненный данными проект. Например тестируем работу админчасти статистики, надо иметь готовый массив данных, с которым оперируем и проверяем результаты. Неудобство состоит в том, что эти фикстуры надо где взять, надо поддерживать актуальными, такими-же актуальными как и тесты. Вот как раз и про неудобную часть, а также паре подводных камней я бы и хотел вам рассказать.

    Получаем фикстуру

    1. python manage.py dumpdata > all_data.json

    Рассказывать, что это означает я не буду, но то есть хорошая документация по фикстурам у самой Django .

    Я «обплетал» тесты уже готового написанного проекта. В нагрузку с проектом идет дамп базы, которая, как это не удивительно, может быть не целостная. Самый часты бок — это когда записи по форенключу нет. Например у Вас есть профиль, но нет юзера или есть транзакция между не существующими счетами.

    Самое обидное, что Джанго Вам не поможет решить эту проблему. И получите что-то типа
    Error: Unable to serialize database:

    Нагугил тикет в Django Code:
    https://code.djangoproject.com/ticket/6773

    К которому прилагается команда, которая показывает Вам «разбитые модели», т. е. модели не полные с неверными данными в ForeignKey .
    Я ее немного приукрасил возможностью удалять их автоматом https://gist.github.com/1018947. Для реальных данных удаление автоматом — это не очень обдуманный шаг, но мне сейчас надо получить хоть какую-то фикстуру.

    Подержание актуальности фикстуры

    Для поддержки актуальности базы между всеми разработчиками используется django-south, мне кажется это уже давно стало стандартом Django разработки. Тот же механизм можно использовать для поддержки актуальности с фикстурами, поэтому я одну фикстуру полностью перегоняю в sqlite3 базу, которую как и фикстуру держу в репозитарии проекта и для доступа к которой использую отдельный сетингс.

    Сеттингс файл для этого состоит из 3х строчек (settings_lights.py):

    1. from settings import *
    2. DATABASES['default']['ENGINE'] ='django.db.backends.sqlite3'
    3. DATABASES['default']['NAME'] = 'lights.db'

    Как известно, в любую команду можно передать не стандартное имя сетингс модуля.

    Например, для того, чтоб запустить его и добавить новых данных:

    1. python manage.py runserver 0:8001settings=settings_lights
    2. python manage.py dumpdata –setting=settings_lights > all_data.json

    А поддерживать актуальность фикстуры можно через миграции, которые вы создаете после изменения структуры базы

    1. python manage.py migrate –settings=settings_lights
    2. python manage.py dumpdata –setting=settings_lights > all_data.json

    Тестирование

    Для тестирования я использую тот-же settings_lights.py для того, чтобы использовать sqlite3 в тестах, при этом для тестов вся база будет держаться в памяти, что существенно ускорит процесс написания тестов и тестирования их.

    1. python manage.py testsettings=settings_lights

    Но я думаю как финальную проверку, после того, как вы закончили с разработкой ( доработкой ) тестов можно использовать и реальный Engine.

    1. python manage.py test

    А собственно сам текст тестов может выглядить так:

    1. from django.test import TestCase
    2. from django.test.client import Client
    3. from django.contrib.auth.models import User
    4.  
    5. class SimpleTest(TestCase):
    6.     fixtures = ['all_data.json']
    7.     def setUp(self):      
    8.         self.client = Client()
    9.  
    10.     def test_details(self):
    11.         print User.objects.all()

    Этот пример ничего не тестирует, а просто показывает Вам, что данные на момент запуска тестов в базе уже есть. Фикстуры можно хранить как в папке fixtures любой апы, не только тестируемой. А еще в сетингсах можно прописать:

    1. FIXTURE_DIRS = (
    2.    '/path/to/myapp/fixtures/',
    3. )

    Проблема с сигналами

    Про сигналы в Django вы можете почитать в документции.

    Фикстура — это по сути сериализация ОРМ объектов, т. е. объект будет сохранен как json, как просто текст. А значит загрузка из фикстуры — это поочередное добавление всех объектов, а добавление объектов связано с вызовом сигналов, которые в свою очередь могу сами создавать объекты моделей или изменять существующие.

    Например. У Вас есть 2 модели счета и транзакции. При добавлении транзакции — дергается сигнал, по которому изменяются балансы счетов участников этой транзакции. При подготовке фикстуры вы создали одну транзакцию между двумя счетами на сумму 100 рублей, т. е. после ее проведения на одном счету прибавится 100 рублей, а на другой вычтится. Вы сохраните полученные данные в файл фикстуры, в которой будут готовые записи со счетами и транзакциями. Во время тестирования этот файл будет загружаться и вначале загрузятся модели счетов – на одном 100, на другом -100. После загрузятся транзакции и дернут сигнал, который еще раз изменит балансы на счетах и мы во время тестирования увидим состояния на счетах 200 и -200.

    Решение у джанги есть , но почему-то не документированное, и как по мне — очень не удачное.

    В обработчик сигнала передается параметр raw который True во время загрузки фиксутры.

    Так что, если вы не хотите, чтоб обработчик сигнала работал в момент загрузки фикстуры, то первые 3 строчки вашего обработчика могут выглядит так:

    1. def trans_save(sender, instance, raw,  **kwargs):                                                                                                                                  
    2.     if raw:                
    3.         return

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

    У меня все. Я описал то, как с фикстурами работаю я, и очевидно, что они могут сэкономить очень много времени Вам при разработке тестов, а также могут помогать Вам делать более качественные и реальные тесты.

    Хотелось бы в комментариях увидеть критику такого подхода, дополнения, подводные камни, с которым вы сталкиваетесь. Буду дополнять статью Вашими цитатами и идеями.

    Спасибо, и удачных Вам выходных.

    Share and Enjoy:
    • Facebook
    • LinkedIn
    • Twitter
    • del.icio.us
    • StumbleUpon
    • MySpace
    • Reddit
    • Digg
    • Google Bookmarks
    • Technorati
    • email
    • Print
    • Sphinn
    • Mixx
    • Blogplay
    • Add to favorites
    • Linkter
    • Live
    • MSN Reporter
    • NewsVine
    • RSS
    • Yahoo! Bookmarks
    • Yahoo! Buzz
    • Yigg
    Rating 3.00 out of 5
    [?]

    Tags: , , ,

  • 04Aug

    kombainПоследнее время мучает одна идея. Мне как всегда кажется, что она реально не нова, и если это так, скажите мне, как это дело называется.

    Если помните, я уже писал о тестировании наследниками. Теперь у меня родилась идея организации инлайн тестов, также намного своеобразным способом.

    Что если проект разделить на 2, или создавать 2 его копии. Один будет для боевого сервера, а второй будет для сервера разработки. И проект для разработки будет отличатся от проекта для боевого только тем, что в него будут включены проверки входных данных для всех блоков программы ( или критичных, или точек взаимодействия модулей ), это бывает полезно, когда несколько человек работает на одним проекта и надо проверять, чтоб один разработки пользовался интерфейсом модуля другого разработчика правильно. Но все эти проверки не нужны на боевом, или не нужны с определенного времени. Т.е. чтоб от них можно было легко избавится и легко включить обратно.

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

    Задача решается на python.

    У python есть массив каталогов, в которых он ищет модуль, как только вы хотите его импортировать. И этот список каталогов можно расширять во время работы программы.

    1. import sys
    2. sys.path.apend('/home/oduvan/list_of_my_cool_modules')

    Python позволяет один и тот же модуль импортировать с разными именами. Т.е. в области видимости, в которую мы импортируем модуль — мы можем дать ему несколько различных имен.

    1. import sys
    2. import sys as new_sys

    И можно все имена из одного модуля импортировать в другой модуль.

    1. from sys import *

    Вот по сути и весь необходимый функционал от python, который необходим для реализации этой идеи. Т.е. предполагаю, что любой другой язык с ООП, который имеет эти возможности может использовать эту идею.

    Решение.

    Модули для боевого сервера — будут включать в себя только функционал, т.е без каких либо проверок. А модули для деволова будут включать только проверки и никакого функционала. В них будут собраны функции, которые будут проверять входных параметры одноименных функций из девелопа. При чем возможности проверок вообще ничем не ограничены.

    Ниже приведена структура каталогов и файлов нашего проекта.

    modules
    	__init__.py
    	develop
    		__init__.py
    		t1.py
    	production
    		__init__.py
    		t1.py
    		t2.py
    run.py
    

    В modules/production собраны все модули для боевого сервера
    В modules/develop модули с проверками данных
    run.py скрипт, в котором они будут использоваться.

    Замете, что в папке modules тоже есть файл __init__.py, т.е. он также будет точкой отсчета для импорта.

    В папке develop есть файл t1.py для тестирования такого же модуля из продакшена, или его части. А также мы видим, что модуль t2 остается без тестов.

    ( Автор просит прощения, за такие без звучные имена, с фантазией у него совсем все плохо )

    Приведу пример модуля t1 из production

    1. def p_name():
    2.     print 'Production'
    3.    
    4. def just_in_prod():
    5.     print 'This just in prod'
    6.  
    7. def sum2(a,b):
    8.     return a+b
    9.    
    10. class A(object):
    11.     def b_prop(self,b):
    12.         return b.prop
    13.  
    14. class B(object):
    15.     def __init__(self,prop):
    16.         self.prop = prop

    функция p_name просто нужна для вывода имени модуля или репозитария, для нас она особой ценности не имеет.
    just_in_prod — тестироваться этой функции проводится не будет
    sum2 — складывает 2 числа. И мы хотим проверить, чтоб это на самом деле были 2 числа.
    Объект класс A имеет метод, который на вход получает один параметр — объект класса B, и возвращает его свойство. Класс B мы также тестировать не будем.

    Как сами уже можете видеть, в модуле нет ни одного слово о тестах. Вы видите чистый функционал.

    Теперь приведу вам пример модуля t1 из develop

    1. from production.t1 import *
    2. import production.t1 as P
    3. def p_name():
    4.     print 'Develop'
    5.     P.p_name()
    6.  
    7. def sum2(a,b):
    8.     if not isinstance(a, (int,float)):
    9.         raise ValueError('a must be int or float')
    10.     if not isinstance(b, (int,float)):
    11.         raise ValueError('a must be int or float')
    12.     return P.sum2(a,b)
    13.    
    14.    
    15. class A(P.A):
    16.     def b_prop(self,b,*args,**kwargs):
    17.         if not isinstance(b, B):
    18.             raise ValueError('first argument must be instance of B')
    19.         return super(A,self).b_prop(b,*args,**kwargs)

    Первые 2 строчки всегда должны быть в модуле тестирования.

    Функция p_name будет чисто информативная ( мы на ней покажем вам порядок импортирования) , и безусловно тестирование без нее лучше обойтись.

    Проверка для функции sum2 и проверка для свойства b_prop из класса A.

    Ну и текст скрипта, который демонстрирует процесс работы и тестирования run.py. Предполагается, что у вас это и будет точкой отсчета для запуска.

    1. import sys
    2. sys.path.append('modules/develop')
    3. sys.path.append('modules')
    4. sys.path.append('modules/production')
    5.  
    6. import t1,t2
    7. t1.p_name()
    8. print '—'
    9. t1.just_in_prod()
    10. t2.in_only_production()
    11. print '—'
    12. o_a = t1.A()
    13. o_b = t1.B('it_s prop')
    14. print o_a.b_prop(o_b)
    15.  
    16. print t1.sum2(1,2)
    17.  
    18. print o_a.b_prop(15)

    В этом скрипте важным является порядок добавления модулей в массив sys.path, ну и то, чтоб эти строчки были самые первые в вашем скрипте.

    Вывод скрипта будет следующий

    Develop
    Production
    ---
    This just in prod
    Only in production
    ---
    it_s prop
    3
    Traceback (most recent call last):
      File "run.py", line 18, in 
        print o_a.b_prop(15)
      File "modules/develop/t1.py", line 18, in b_prop
        raise ValueError('first argument must be instance of B')
    ValueError: first argument must be instance of B
    

    для боевого сервера он будет такой же, только в «шапке» скрипта будет не

    1. import sys
    2. sys.path.append('modules/develop')
    3. sys.path.append('modules')
    4. sys.path.append('modules/production')

    а

    1. import sys
    2. sys.path.append('modules/production')

    В этом случае поменяется только текст ошибки

    Production
    ---
    This just in prod
    Only in production
    ---
    it_s prop
    3
    Traceback (most recent call last):
      File "run.py", line 18, in 
        print o_a.b_prop(15)
      File "modules/production/t1.py", line 12, in b_prop
        return b.prop
    AttributeError: 'int' object has no attribute 'prop'
    

    Я думаю люди, которые часами разбирались в мало понятных ошибках модулей — оценят идею.

    Да, и еще одним плюсом является то, что вы можете делать легко несколько профилей тестирования и легко между ними переключатся, Добавив рядом с папкой develop, что нить типа develop_plusplus, или если вы хотите залить на продакшн тесты, но не хотите тестировать все, а только те чатити, которые подвержены изменения develop_light.

    Я же планирую у себя это использовать для своего сокет сервера. У меня именно так разнесен скрипт run.pay от функциональных модулей. И думаю при запуске его добавить еще параметр -t которому можно будет передать имя профиля тестирования.

    Файлы из примера split_develop.tar

    Вот и все. Жду жесткую критику, предложения по улучшению.

    Спасибо, что дочитали до конца :)

    Share and Enjoy:
    • Facebook
    • LinkedIn
    • Twitter
    • del.icio.us
    • StumbleUpon
    • MySpace
    • Reddit
    • Digg
    • Google Bookmarks
    • Technorati
    • email
    • Print
    • Sphinn
    • Mixx
    • Blogplay
    • Add to favorites
    • Linkter
    • Live
    • MSN Reporter
    • NewsVine
    • RSS
    • Yahoo! Bookmarks
    • Yahoo! Buzz
    • Yigg
    Rating 3.00 out of 5
    [?]

    Tags: , , ,

  • 15Apr

    crashtestdummyКоротенько изложу идею. И как всегда, уверен, что она не нова, т.к. ничего особенного в ней нет.

    Итак. Тестирование. Для меня синоним закрепления функционала. И тесты я как правило пишу, когда у меня утряслась бизнес логика и архитектура проекта, и даже отчасти написана документация ( в тех местах, где было не лень). Когда у вас просто класс, который супирует 2 числа и выдает результат, то написать автоматические тесты – не проблема. У меня стала задача написание автоматических тестов для сетевого приложения.

    Общая задача его сводится к следующему. К нему по очереди цепляются клиенты, оставляют какие то данные. Сервер обрабатывает и выдает инфу следующим клиентам. Это общая задача для любого сетевого приложения + есть игровой элемент, когда есть событие, которое может возникнуть с некой вероятностью. Как организовать тест. Первое, что пришло в голову — это тестируемые программы, которые подключаются к этому серваку, прогоняют функционал, проверяют ответы, выдают результат. Но можно проще. Тестируемое приложение имеет базовый класс. В котором создаётся объект сокет сервера и объект коннекта к бд. Мы создаем наследника от него, в котором переопределяем функции коннекта и обработки данных с сокет сервера, а функции эти просто сохраняют эти данные в отдельной переменной. Таким образом мы уже тестирует сам функционал, а не систему взаимодействия (которую изначально рассматриваем как черный ящик), и все тестирование по сути сводится к суммированию двух чисел. Кроме того — вы можете сразу выделить вероятностный функционал, например который генерит рандомное значение, и его также переопределить, чтоб данные не генерировались, а брались из заданного пула значений.

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

    Вот и все. Ваше мнение?

    Share and Enjoy:
    • Facebook
    • LinkedIn
    • Twitter
    • del.icio.us
    • StumbleUpon
    • MySpace
    • Reddit
    • Digg
    • Google Bookmarks
    • Technorati
    • email
    • Print
    • Sphinn
    • Mixx
    • Blogplay
    • Add to favorites
    • Linkter
    • Live
    • MSN Reporter
    • NewsVine
    • RSS
    • Yahoo! Bookmarks
    • Yahoo! Buzz
    • Yigg
    Rating 3.00 out of 5
    [?]

    Tags: , ,

   

Recent Posts

Recent Comments

  • Благодарю, начал изучать fabric с вашей статьи....
  • Идея действительно отличная и очень радует то, что подобн...
  • Спасибо...
  • Там четыре круглых кнопочки. Подразумевается, что каждая ...
  • А в чем заключатеся неправильна работа?...