(機械翻訳)テストの追加

新しい `` tests``サブパッケージにモデルとビューのテストといくつかの機能テストを追加します。テストでは、アプリケーションが正常に動作していることを確認し、将来変更が加えられた場合でも動作し続けます。

ファイル tests.pyalchemy クッキーカッターの一部として生成されましたが、テストが tests サブパッケージに置かれるのは一般的なことです。テストサブパッケージの各モジュールには、アプリケーションの対応するモジュールのテストが含まれている必要があります。テストモジュールに test_ という接頭辞が付いていることを除いて、それぞれの対応するモジュールのペアは同じ名前でなければなりません。

まず tests.py を削除し、新しいテストと新しい空のファイル tests/__init__.py を含む新しいディレクトリを作成します。

警告

Pythonモジュールをパッケージにリファクタリングして、キャッシュファイル( `` .pyc``ファイルや `` __pycache__``フォルダ)を確実に削除することは非常に重要です! Pythonは、古いコードを使用して、フォルダに移動する前にキャッシュファイルの優先順位を決定します。なぜあなたの変更がうまくいかないのだろうか?

ビューをテストする

新しい `` tests / test_views.py``ファイルを作成し、他のテストクラスのベースとして使われる `` BaseTest``クラスを追加します。次に、以前にアプリケーションに追加した各ビュー関数のテストを追加します。 `` ViewWikiTests``、 `` ViewPageTests``、 `` AddPageTests``、および `` EditPageTests``の4つのテストクラスを追加します。これらは `` view_wiki``、 `` view_page``、 `` add_page``、 `` edit_page``のビューをテストします。

機能テスト

単体テストでテストされていないセキュリティ面(ログイン、ログアウト、 basic ユーザーは作成していないページを編集できないことを確認しますが、 editor ユーザーはできる、など。

`` tests``サブパッケージに対するすべての編集の結果を表示します。

次のように表示されるように `` tutorial/tests/test_views.py`` を作成します:

  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
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import unittest
import transaction

from pyramid import testing


def dummy_request(dbsession):
    return testing.DummyRequest(dbsession=dbsession)


class BaseTest(unittest.TestCase):
    def setUp(self):
        from ..models import get_tm_session
        self.config = testing.setUp(settings={
            'sqlalchemy.url': 'sqlite:///:memory:'
        })
        self.config.include('..models')
        self.config.include('..routes')

        session_factory = self.config.registry['dbsession_factory']
        self.session = get_tm_session(session_factory, transaction.manager)

        self.init_database()

    def init_database(self):
        from ..models.meta import Base
        session_factory = self.config.registry['dbsession_factory']
        engine = session_factory.kw['bind']
        Base.metadata.create_all(engine)

    def tearDown(self):
        testing.tearDown()
        transaction.abort()

    def makeUser(self, name, role, password='dummy'):
        from ..models import User
        user = User(name=name, role=role)
        user.set_password(password)
        return user

    def makePage(self, name, data, creator):
        from ..models import Page
        return Page(name=name, data=data, creator=creator)


class ViewWikiTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
        self.config.include('..routes')

    def tearDown(self):
        testing.tearDown()

    def _callFUT(self, request):
        from tutorial.views.default import view_wiki
        return view_wiki(request)

    def test_it(self):
        request = testing.DummyRequest()
        response = self._callFUT(request)
        self.assertEqual(response.location, 'http://example.com/FrontPage')


class ViewPageTests(BaseTest):
    def _callFUT(self, request):
        from tutorial.views.default import view_page
        return view_page(request)

    def test_it(self):
        from ..routes import PageResource

        # add a page to the db
        user = self.makeUser('foo', 'editor')
        page = self.makePage('IDoExist', 'Hello CruelWorld IDoExist', user)
        self.session.add_all([page, user])

        # create a request asking for the page we've created
        request = dummy_request(self.session)
        request.context = PageResource(page)

        # call the view we're testing and check its behavior
        info = self._callFUT(request)
        self.assertEqual(info['page'], page)
        self.assertEqual(
            info['content'],
            '<div class="document">\n'
            '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
            'CruelWorld</a> '
            '<a href="http://example.com/IDoExist">'
            'IDoExist</a>'
            '</p>\n</div>\n')
        self.assertEqual(info['edit_url'],
                         'http://example.com/IDoExist/edit_page')


class AddPageTests(BaseTest):
    def _callFUT(self, request):
        from tutorial.views.default import add_page
        return add_page(request)

    def test_it_pageexists(self):
        from ..models import Page
        from ..routes import NewPage
        request = testing.DummyRequest({'form.submitted': True,
                                        'body': 'Hello yo!'},
                                       dbsession=self.session)
        request.user = self.makeUser('foo', 'editor')
        request.context = NewPage('AnotherPage')
        self._callFUT(request)
        pagecount = self.session.query(Page).filter_by(name='AnotherPage').count()
        self.assertGreater(pagecount, 0)

    def test_it_notsubmitted(self):
        from ..routes import NewPage
        request = dummy_request(self.session)
        request.user = self.makeUser('foo', 'editor')
        request.context = NewPage('AnotherPage')
        info = self._callFUT(request)
        self.assertEqual(info['pagedata'], '')
        self.assertEqual(info['save_url'],
                         'http://example.com/add_page/AnotherPage')

    def test_it_submitted(self):
        from ..models import Page
        from ..routes import NewPage
        request = testing.DummyRequest({'form.submitted': True,
                                        'body': 'Hello yo!'},
                                       dbsession=self.session)
        request.user = self.makeUser('foo', 'editor')
        request.context = NewPage('AnotherPage')
        self._callFUT(request)
        page = self.session.query(Page).filter_by(name='AnotherPage').one()
        self.assertEqual(page.data, 'Hello yo!')


class EditPageTests(BaseTest):
    def _callFUT(self, request):
        from tutorial.views.default import edit_page
        return edit_page(request)

    def makeContext(self, page):
        from ..routes import PageResource
        return PageResource(page)

    def test_it_notsubmitted(self):
        user = self.makeUser('foo', 'editor')
        page = self.makePage('abc', 'hello', user)
        self.session.add_all([page, user])

        request = dummy_request(self.session)
        request.context = self.makeContext(page)
        info = self._callFUT(request)
        self.assertEqual(info['pagename'], 'abc')
        self.assertEqual(info['save_url'],
                         'http://example.com/abc/edit_page')

    def test_it_submitted(self):
        user = self.makeUser('foo', 'editor')
        page = self.makePage('abc', 'hello', user)
        self.session.add_all([page, user])

        request = testing.DummyRequest({'form.submitted': True,
                                        'body': 'Hello yo!'},
                                       dbsession=self.session)
        request.context = self.makeContext(page)
        response = self._callFUT(request)
        self.assertEqual(response.location, 'http://example.com/abc')
        self.assertEqual(page.data, 'Hello yo!')

次のように表示されるように `` tutorial / tests / test_functional.py``を作成します:

  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
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import transaction
import unittest
import webtest


class FunctionalTests(unittest.TestCase):

    basic_login = (
        '/login?login=basic&password=basic'
        '&next=FrontPage&form.submitted=Login')
    basic_wrong_login = (
        '/login?login=basic&password=incorrect'
        '&next=FrontPage&form.submitted=Login')
    basic_login_no_next = (
        '/login?login=basic&password=basic'
        '&form.submitted=Login')
    editor_login = (
        '/login?login=editor&password=editor'
        '&next=FrontPage&form.submitted=Login')

    @classmethod
    def setUpClass(cls):
        from tutorial.models.meta import Base
        from tutorial.models import (
            User,
            Page,
            get_tm_session,
        )
        from tutorial import main

        settings = {
            'sqlalchemy.url': 'sqlite://',
            'auth.secret': 'seekrit',
        }
        app = main({}, **settings)
        cls.testapp = webtest.TestApp(app)

        session_factory = app.registry['dbsession_factory']
        cls.engine = session_factory.kw['bind']
        Base.metadata.create_all(bind=cls.engine)

        with transaction.manager:
            dbsession = get_tm_session(session_factory, transaction.manager)
            editor = User(name='editor', role='editor')
            editor.set_password('editor')
            basic = User(name='basic', role='basic')
            basic.set_password('basic')
            page1 = Page(name='FrontPage', data='This is the front page')
            page1.creator = editor
            page2 = Page(name='BackPage', data='This is the back page')
            page2.creator = basic
            dbsession.add_all([basic, editor, page1, page2])

    @classmethod
    def tearDownClass(cls):
        from tutorial.models.meta import Base
        Base.metadata.drop_all(bind=cls.engine)

    def test_root(self):
        res = self.testapp.get('/', status=302)
        self.assertEqual(res.location, 'http://localhost/FrontPage')

    def test_FrontPage(self):
        res = self.testapp.get('/FrontPage', status=200)
        self.assertTrue(b'FrontPage' in res.body)

    def test_unexisting_page(self):
        self.testapp.get('/SomePage', status=404)

    def test_successful_log_in(self):
        res = self.testapp.get(self.basic_login, status=302)
        self.assertEqual(res.location, 'http://localhost/FrontPage')

    def test_successful_log_in_no_next(self):
        res = self.testapp.get(self.basic_login_no_next, status=302)
        self.assertEqual(res.location, 'http://localhost/')

    def test_failed_log_in(self):
        res = self.testapp.get(self.basic_wrong_login, status=200)
        self.assertTrue(b'login' in res.body)

    def test_logout_link_present_when_logged_in(self):
        self.testapp.get(self.basic_login, status=302)
        res = self.testapp.get('/FrontPage', status=200)
        self.assertTrue(b'Logout' in res.body)

    def test_logout_link_not_present_after_logged_out(self):
        self.testapp.get(self.basic_login, status=302)
        self.testapp.get('/FrontPage', status=200)
        res = self.testapp.get('/logout', status=302)
        self.assertTrue(b'Logout' not in res.body)

    def test_anonymous_user_cannot_edit(self):
        res = self.testapp.get('/FrontPage/edit_page', status=302).follow()
        self.assertTrue(b'Login' in res.body)

    def test_anonymous_user_cannot_add(self):
        res = self.testapp.get('/add_page/NewPage', status=302).follow()
        self.assertTrue(b'Login' in res.body)

    def test_basic_user_cannot_edit_front(self):
        self.testapp.get(self.basic_login, status=302)
        res = self.testapp.get('/FrontPage/edit_page', status=302).follow()
        self.assertTrue(b'Login' in res.body)

    def test_basic_user_can_edit_back(self):
        self.testapp.get(self.basic_login, status=302)
        res = self.testapp.get('/BackPage/edit_page', status=200)
        self.assertTrue(b'Editing' in res.body)

    def test_basic_user_can_add(self):
        self.testapp.get(self.basic_login, status=302)
        res = self.testapp.get('/add_page/NewPage', status=200)
        self.assertTrue(b'Editing' in res.body)

    def test_editors_member_user_can_edit(self):
        self.testapp.get(self.editor_login, status=302)
        res = self.testapp.get('/FrontPage/edit_page', status=200)
        self.assertTrue(b'Editing' in res.body)

    def test_editors_member_user_can_add(self):
        self.testapp.get(self.editor_login, status=302)
        res = self.testapp.get('/add_page/NewPage', status=200)
        self.assertTrue(b'Editing' in res.body)

    def test_editors_member_user_can_view(self):
        self.testapp.get(self.editor_login, status=302)
        res = self.testapp.get('/FrontPage', status=200)
        self.assertTrue(b'FrontPage' in res.body)

    def test_redirect_to_edit_for_existing_page(self):
        self.testapp.get(self.editor_login, status=302)
        res = self.testapp.get('/add_page/FrontPage', status=302)
        self.assertTrue(b'FrontPage' in res.body)

次のように表示されるように `` tutorial / tests / test_initdb.py``を作成します:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import os
import unittest


class TestInitializeDB(unittest.TestCase):

    def test_usage(self):
        from ..scripts.initializedb import main
        with self.assertRaises(SystemExit):
            main(argv=['foo'])

    def test_run(self):
        from ..scripts.initializedb import main
        main(argv=['foo', 'development.ini'])
        self.assertTrue(os.path.exists('tutorial.sqlite'))
        os.remove('tutorial.sqlite')

次のように表示されるように `` tutorial / tests / test_security.py``を作成します:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import unittest
from pyramid.testing import DummyRequest


class TestMyAuthenticationPolicy(unittest.TestCase):

    def test_no_user(self):
        request = DummyRequest()
        request.user = None

        from ..security import MyAuthenticationPolicy
        policy = MyAuthenticationPolicy(None)
        self.assertEqual(policy.authenticated_userid(request), None)

    def test_authenticated_user(self):
        from ..models import User
        request = DummyRequest()
        request.user = User()
        request.user.id = 'foo'

        from ..security import MyAuthenticationPolicy
        policy = MyAuthenticationPolicy(None)
        self.assertEqual(policy.authenticated_userid(request), 'foo')

次のように表示されるように `` tutorial / tests / test_user_model.py``を作成します:

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import unittest
import transaction

from pyramid import testing


class BaseTest(unittest.TestCase):

    def setUp(self):
        from ..models import get_tm_session
        self.config = testing.setUp(settings={
            'sqlalchemy.url': 'sqlite:///:memory:'
        })
        self.config.include('..models')
        self.config.include('..routes')

        session_factory = self.config.registry['dbsession_factory']
        self.session = get_tm_session(session_factory, transaction.manager)

        self.init_database()

    def init_database(self):
        from ..models.meta import Base
        session_factory = self.config.registry['dbsession_factory']
        engine = session_factory.kw['bind']
        Base.metadata.create_all(engine)

    def tearDown(self):
        testing.tearDown()
        transaction.abort()

    def makeUser(self, name, role):
        from ..models import User
        return User(name=name, role=role)


class TestSetPassword(BaseTest):

    def test_password_hash_saved(self):
        user = self.makeUser(name='foo', role='bar')
        self.assertFalse(user.password_hash)

        user.set_password('secret')
        self.assertTrue(user.password_hash)


class TestCheckPassword(BaseTest):

    def test_password_hash_not_set(self):
        user = self.makeUser(name='foo', role='bar')
        self.assertFalse(user.password_hash)

        self.assertFalse(user.check_password('secret'))

    def test_correct_password(self):
        user = self.makeUser(name='foo', role='bar')
        user.set_password('secret')
        self.assertTrue(user.password_hash)

        self.assertTrue(user.check_password('secret'))

    def test_incorrect_password(self):
        user = self.makeUser(name='foo', role='bar')
        user.set_password('secret')
        self.assertTrue(user.password_hash)

        self.assertFalse(user.check_password('incorrect'))

注釈

我々は優れた WebTest パッケージを利用してアプリケーションの機能テストを行っています。これは `` setup.py``の `` tests_require``セクションで定義されています。テスト目的だけに必要な他の依存関係はそこに追加することができ、 `` setup.py test``を実行すると自動的にインストールされます。

テストの実行

これらのテストは、テストを実行する と同じように実行できますが、最初にSQLiteデータベース` tutorial.sqlite`を削除します。データベースを削除しないと、テスト実行時に整合性エラーが表示されます。

UNIXの場合:

$ rm tutorial.sqlite
$ $VENV/bin/py.test -q

Windowsの場合:

c:\tutorial> del tutorial.sqlite
c:\tutorial> %VENV%\Scripts\py.test -q

予想される結果は、次のようになります。

................................
32 passed in 9.90 seconds