Konstantine Rybnikov avatar Konstantine Rybnikov committed 94b8d3b

add everything we need

Comments (0)

Files changed (6)

 .emacs-project
 *.pyc
 dist/
+docs/_build/

docs/code/unit.py

+# -*- coding: utf-8 -*-
+
+"""app.bl.user"""
+
+from app.bl import mail
+from app.tasks.friendship import discover_possible_friends
+from app.models import User
+from app.utils.security import not_md5_and_has_salt
+
+
+def create_user(email, password, full_name):
+    user = User(email=email,
+                password=not_md5_and_has_salt(password),
+                full_name=full_name)
+    user.save()
+    score = count_score(user)
+    if score < 10:
+        choose_low_quality_avatar(user)
+    else:
+        choose_high_quality_avatar(user)
+        mail.send_welcome_email(user)
+        discover_possible_friends(user)
+    return user
+
+
+def count_score(user):
+    pass
+
+
+def choose_low_quality_avatar(user):
+    pass
+
+
+def choose_high_quality_avatar(user):
+    pass

docs/code/unit_test_mock.py

+# -*- coding: utf-8 -*-
+
+import unittest
+from mock import patch
+from app.bl.user import create_user
+
+
+class TestCreateUser(unittest.TestCase):
+    @patch('app.bl.user.choose_low_quality_avatar', autospec=True)
+    @patch('app.bl.user.count_score', autospec=True)
+    @patch('app.bl.user.not_md5_and_has_salt', autospec=True)
+    @patch('app.bl.user.User', autospec=True)
+    def test_should_create_save_and_return_user(
+        self, user_mock, not_md5_and_has_salt_mock, count_score_mock,
+        choose_low_quality_avatar_mock):
+
+        count_score_mock.return_value = 0
+        user = user_mock.return_value
+
+        # do
+        rv = create_user("foo@bar.com", "pwd", "Foo Bar")
+        user_mock.assert_called_with(
+            email="foo@bar.com",
+            password=not_md5_and_has_salt_mock.return_value,
+            full_name="Foo Bar")
+        not_md5_and_has_salt_mock.assert_called_with("pwd")
+        user.save.assert_called_with()
+        self.assertIs(rv, user)
+
+    @patch('app.bl.user.choose_low_quality_avatar', autospec=True)
+    @patch('app.bl.user.count_score', autospec=True)
+    @patch('app.bl.user.not_md5_and_has_salt', autospec=True)
+    @patch('app.bl.user.User', autospec=True)
+    def test_should_choose_low_quality_avatar_on_small_score(
+        self, user_mock, not_md5_and_has_salt_mock, count_score_mock,
+        choose_low_quality_avatar_mock):
+
+        count_score_mock.return_value = 9
+        user = user_mock.return_value
+
+        # do
+        create_user("foo@bar.com", "pwd", "Foo Bar")
+
+        count_score_mock.assert_called_with(user)
+        choose_low_quality_avatar_mock.assert_called_with(user)
+
+    @patch('app.bl.user.discover_possible_friends', autospec=True)
+    @patch('app.bl.user.mail', autospec=True)
+    @patch('app.bl.user.choose_high_quality_avatar', autospec=True)
+    @patch('app.bl.user.count_score', autospec=True)
+    @patch('app.bl.user.not_md5_and_has_salt', autospec=True)
+    @patch('app.bl.user.User', autospec=True)
+    def test_should_choose_high_quality_avatar_on_big_score(
+        self, user_mock, not_md5_and_has_salt_mock, count_score_mock,
+        choose_high_quality_avatar_mock, mail_mock,
+        discover_possible_friends_mock):
+
+        # ok, I'm bored already
+        pass

docs/code/unit_test_mockstar.py

+# -*- coding: utf-8 -*-
+
+from mockstar import BaseTestCase
+from mockstar import prefixed_p
+from app.bl.user import create_user
+
+ppatch = prefixed_p('app.bl.user', autospec=True)
+
+
+class TestCreateUser(BaseTestCase):
+    @ppatch('discover_possible_friends')
+    @ppatch('mail')
+    @ppatch('choose_high_quality_avatar')
+    @ppatch('choose_low_quality_avatar')
+    @ppatch('count_score')
+    @ppatch('not_md5_and_has_salt')
+    @ppatch('User')
+    def side_effects(self, se):
+        se.user = se.User.return_value
+        se.secure_pwd = se.not_md5_and_has_salt.return_value
+        se.score = se.count_score.return_value
+        return self.invoke(se)
+
+    def test_should_create_save_and_return_user(self, se):
+        # do
+        rv = create_user("foo@bar.com", "pwd", "Foo Bar")
+
+        se.User.assert_called_with(
+            email="foo@bar.com",
+            password=se.secure_pwd,
+            full_name="Foo Bar")
+        se.not_md5_and_has_salt.assert_called_with("pwd")
+        self.assertIs(rv, se.user)
+
+    def test_should_choose_low_quality_avatar_on_small_score(self, se):
+        se.count_score.return_value = 9
+
+        # do
+        create_user("foo@bar.com", "pwd", "Foo Bar")
+
+        se.count_score.assert_called_with(se.user)
+        se.choose_low_quality_avatar.assert_called_with(se.user)
+
+    def test_should_choose_high_quality_avatar_on_big_score(self, se):
+        se.count_score.return_value = 11
+
+        # do
+        create_user("foo@bar.com", "pwd", "Foo Bar")
+
+        se.choose_high_quality_avatar.assert_called_with(se.user)
+
+    # I'm not so bored now :)
    You can adapt this file completely to your liking, but it should at least
    contain the root `toctree` directive.
 
-Welcome to mockstar's documentation!
-====================================
+======================================
+ Mockstar -- Mocking Like a Rockstar!
+======================================
 
-Contents:
+Mockstar is a bunch of small enhances on top of `Mock
+<http://www.voidspace.org.uk/python/mock/mock.html>`_ library that can
+give you a lot of mocking and unit-testing goods.
 
-.. toctree::
-   :maxdepth: 2
+.. note::
 
+   Mockstar is still in development, but my team already uses it in
+   our project heavily, so it should get stable API soon.
 
+Unit-testing with MockStar
+==========================
+
+So, you want to implement and test your unit. Let's say it's a
+function :func:`create_user` that will look like this when it is done:
+
+.. literalinclude:: code/unit.py
+
+This unit consists of input-parameters:
+
+- email
+- password
+- full_name
+
+and seven side-effects:
+
+- ``User`` model
+- ``not_md5_and_has_salt`` function
+- ``count_score`` function
+- ``choose_low_quality_avatar`` function
+- ``choose_high_quality_avatar`` function
+- ``mail`` business-logic
+- ``discover_possible_friends`` function
+
+So, to test this unit in isolation we would need to mock-out all
+side-effects, on every test put some return-values so that they will
+fit our if-else clauses, and finally, generate suitable
+input-parameters.
+
+With `Mock <http://www.voidspace.org.uk/python/mock/mock.html>`_
+library, you would do something like this:
+
+.. literalinclude:: code/unit_test_mock.py
+
+Problems I see:
+
+- need to repeat mocked names as test parameters
+- need to write autospec=True again and again
+- need to write module prefix ``app.bl.user`` on every patch call
+- need to patch on every test case
+- need to add common return_values and assign to some variables (like
+  user) that we'll use later in asserts
+- side_effects take a lot of space in our testing code, I want to
+  separate them
+
+With mockstar your test would look something like this:
+
+.. literalinclude:: code/unit_test_mockstar.py
+
+I hope you like mockstar's aspiration to fight copy-paste and useless
+typing.
+
+
+..
+   Contents:
+
+   .. toctree::
+      :maxdepth: 2
 
 Indices and tables
 ==================
     return 2 + 3
 
 
+count_score_rv = M()
+
+
+def count_score():
+    return count_score_rv
+
+
+def count_score_caller():
+    return count_score()
+
+
 class TestSideEffectsMethod(BaseTestCase):
+    @ppatch('count_score')
     @ppatch('foo')
     def side_effects(self, se):
         se.something = se.foo.return_value
+        se.four = 4
         return self.invoke(se)
 
-    def test_should_get_foo_mocked(self, se=None):
+    def test_should_get_foo_mocked(self, se):
         self.assertIsInstance(se.foo(), MagicMock)
 
-    def test_should_also_get_foo_mocksed(self, se=None):
+    def test_should_also_get_foo_mocksed(self, se):
         self.assertIsInstance(se.foo(), MagicMock)
 
-    def test_should_see_something(self, se=None):
+    def test_should_see_something(self, se):
         self.assertIs(se.something, se.foo())
 
+    def test_should_change_count_score(self, se):
+        se.count_score.return_value = m = M()
+
+        rv = count_score_caller()
+
+        self.assertIs(rv, m)
+
 
 if __name__ == '__main__':
     unittest.main()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.