Часть 3: юнит- и функциональное тестирование, Hello World в Chameleon
Шаг 04: Каркас представлений
Хватит с нас “hello world”, теперь приступим к работе над Projector'ом. UX-человек обычно имеет ряд представлений, которые нуждаются в прототипировании и отображении в URL-структуру.
Мы хотим сделать этот процесс быстрым и продуктивным.
В этом шаге мы копируем структуру карты сайта такую:
/
… и сделаем ряд URL'ов которые это осуществят. По ходу, мы создаем больше представлений и больше шаблонов.
/about.html
/acme
/people
Цели
- Введение в UX рабочий процесс, для примера, используя произвольные данные
Что ожидается
- “Стандартные” и “именованные” представления
- Больше ZPT конструкций
Шаги
$ cd ../../creatingux; mkdir step04; cd step04
(Неизмененное) Копируем одно в другое — step04/application.py
:
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
def main():
config = Configurator()
config.scan("views")
app = config.make_wsgi_app()
return appif __name__ == '__main__':
app = main()
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()
Далее также здесь — step04/views.py
:
from pyramid.view import view_config
@view_config(renderer="index.pt")
def index_view(request):
return {}@view_config(renderer="about.pt", name="about.html")
def about_view(request):
return {}@view_config(renderer="company.pt", name="acme")
def company_view(request):
return {"company": COMPANY, "projects": PROJECTS}@view_config(renderer="people.pt", name="people")
def people_view(request):
return {"company": COMPANY, "people": PEOPLE}# Dummy data
COMPANY = "ACME, Inc."PEOPLE = [
{'name': 'sstanton', 'title': 'Susan Stanton'},
{'name': 'bbarker', 'title': 'Bob Barker'},
]PROJECTS = [
{'name': 'sillyslogans', 'title': 'Silly Slogans'},
{'name': 'meaninglessmissions', 'title': 'Meaningless Missions'},
]
И здесь — step04/index.pt
:
<html>
<head>
<title>Projector - Home</title>
</head>
<body>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about.html">About Projector</a></li>
<li><a href="/acme">ACME, Inc.</a></li>
<li><a href="/people">People</a></li>
</ul>
<h1>Projector - Home</h1>
</body>
</html>
Дальше здесь — step04/about.pt
:
<html>
<head>
<title>Projector - About</title>
</head>
<body>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about.html">About Projector</a></li>
<li><a href="/acme">ACME, Inc.</a></li>
<li><a href="/people">People</a></li>
</ul>
<h1>Projector - About</h1>
<p>Projector is a simple project management tool capable of hosting
multiple projects for multiple independent companies,
sharing a developer pool between autonomous companies.</p>
</body>
</html>
Здесь — step04/company.pt
:
<html>
<head>
<title>Projector - People</title>
</head>
<body>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about.html">About Projector</a></li>
<li><a href="/acme">ACME Inc.</a></li>
<li><a href="/people">People</a></li>
</ul>
<h1>People</h1>
<ul>
<li tal:repeat="person people">
<a href="${person.name}">${person.title}</a>
</li>
</ul>
</body>
</html>
И наконец, здесь — step04/tests.py
:
import unittest
class ProjectorViewsUnitTests(unittest.TestCase):
def test_hello_view(self):
from views import index_view
result = index_view({})
self.assertEqual(len(result.keys()), 0)def test_about_view(self):
from views import about_view
result = about_view({})
self.assertEqual(len(result.keys()), 0)def test_company_view(self):
from views import company_view
result = company_view({})
self.assertEqual(result["company"], "ACME, Inc.")
self.assertEqual(len(result["projects"]), 2)def test_people_view(self):
from views import people_view
result = people_view({})
self.assertEqual(result["company"], "ACME, Inc.")
self.assertEqual(len(result["people"]), 2)class ProjectorFunctionalTests(unittest.TestCase):
def setUp(self):
from application import main
app = main()
from webtest import TestApp
self.testapp = TestApp(app)def test_home(self):
res = self.testapp.get('/', status=200)
self.failUnless('Home' in res.body)def test_it(self):
res = self.testapp.get('/', status=200)
self.failUnless('Home' in res.body)
res = self.testapp.get('/about.html', status=200)
self.failUnless('autonomous' in res.body)
res = self.testapp.get('/people', status=200)
self.failUnless('Susan' in res.body)
res = self.testapp.get('/acme', status=200)
self.failUnless('Silly Slogans' in res.body)
Дальше пишем:
$ nosetests
что должно выдать нам отчет по 6ти тестам.
Запускаем приложение:
$ python application.py
И радуемся результату в браузере — 127.0.0.1:8080
Дополнительные вопросы
Что произойдет если у вас есть две регистрации представления без @ name атрибута, meaning both as the default?
Правда ли что Chameleon (сейчас, если мы говорим о второй версии) в любом случае лучше для предоставления вам сообщений об ошибках? Попробуйте это, положив немного ошибок в ваши Python выражения.
Будет ли WebTest корректно срабатывать на эти ошибки?
Влияет ли прибавление .html, к вашим URL'ам, на что-нибудь?
Анализ
Мы начали процесс построения пространства URL'ов, которые отображаются в объекты и иерархию, в нашем приложении. На данный момент, мы смоделировали это с использованием представлений.
Несмотря на увеличение количества наших тестов, каждый из них до сих пор очень маленький. Даже такой простой тест будет до сих пор ловить большинство глупых ошибок которые всплывают в процессе начальной разработки. Надеемся, вы найдете для себя nosetests'ы более продуктивными, чем простое кликание.
Тезисы
How do the registrations happen under the hood?
Chameleon, кэширование, и занесение готовых версий на диск
Шаг 05: Создание основного шаблона
Автор: ks_ks