Commits

Konstantine Rybnikov  committed 4a8b57d

unit-testing looks finished

  • Participants
  • Parent commits 37dc17a

Comments (0)

Files changed (1)

File source/launchpad/2012-08-06-uapycon-tdd/index.html

 - что такое фикстуры и в дополнение к ним -- фабрики (factory_boy)
 - фабрики умеют .create() и .build()
 - фабрики -- гарантия быстрого создания модели
+- ВСЁ! неужели это сложнее, чем ручное тестирование фичи?
 - относительно медленный, зато понятно как писать
-- (почти) прямая замена ручному тестированию
 - работая над API-centric приложением действительно видишь
   независимость front- и back-end
 -->
 
 <!--
 - вообще опыт с функциональным тестированием -- в основном легкий, и
-  сюрпризов в нём будет мало. зато много покрытия кода и быстро пишутся.
+  сюрпризов в нём будет мало.
+- много покрытия кода и быстро пишутся.
 - тем не менее -- часто больше походят на "пушкой по воробьям" (чем
   дальше -- тем сильнее. как и при ручном тестировании).
 -->
 # Интеграционные тесты
 
 <!--
+- тоже всё (относительно) понятно
 - берёте ваши фабрики и проверяете результат. некоторую часть
   сайд-эффектов мокаете.
 - фичу не покрывают (всё равно нужны функ. тесты)
 - наконец самое сложное
 - в вебе никто не пишет юнит-тестов, потому что никто не умеет дружить
   с сайд-эффектами
+- все покрывают юнит-тестами "математику", а потом уходят в ступор
 - поэтому первым делом нужно разобраться, как абстрагироваться от
   всего, кроме нашего "юнита"
 -->
 
 <!--
 - посмотрим, как выгядит юнит в питоне
+- не важно, лезет ли `bar` в БД, это всё равно сайд-эффект
 - вызов функции -- это прежде поиск функции в словаре модуля
-- => можем замокать вызов внешней функции
+- как нам "подменить" `bar`, `baz` и `select`?
 -->
 
 <img src="unit.png">
 
 ---
 
+<!--
+- общая техника, для ослабления зависимости как между сущностями, так
+  и модулями
+- модуль (юнит) house нельзя протестировать, не создав kitchen
+-->
+
 # DI -- проблема
 
-    .java
-    class House {
-      private final Kitchen kitchen = new Kitchen();
-      private boolean isLocked;
+    .python
+    class House(object):
+        def __init__(self):
+            self._kitchen = Kitchen()
+            self._is_locked = False
 
-      private boolean isLocked() {
-        return isLocked;
-      }
+        def is_locked(self):
+            return self._is_locked
 
-      private boolean lock() {
-        kitchen.lock();
-        isLocked = true;
-      }
-    }
+        def lock(self):
+            self._kitchen.lock()
+            self._is_locked = True
 
 ---
 
 # DI -- решение
 
-    .java
-    class House {
-      private final Kitchen kitchen;
-      private boolean isLocked;
-      public House(Kitchen kitchen) {
-          this.kitchen = kitchen;
-      }
+    .python
+    class House(object):
+        def __init__(self, kitchen):
+            self._kitchen = _kitchen
+            self._is_locked = False
 
-      private boolean isLocked() {
-        return isLocked;
-      }
+        def is_locked(self):
+            return self._is_locked
 
-      private boolean lock() {
-        kitchen.lock();
-        isLocked = true;
-      }
-    }
+        def lock(self):
+            self._kitchen.lock()
+            self._is_locked = True
 
 ---
 
 - тестируемым дизайном считается внедрение DI для того, чтоб модели
   оставались "листьями" графа зависимостей
 - логика "сборки" зависимостей отделяется от логики элементов сборки
-- хорошим дизайном считается внедрение DI для взаимодействия модулей
+- НО КАКОЙ ЖЕ ЭТО КОШМАР. неужели для `bar`, `baz` и `query` нужно
+  создать по классу?
 -->
 
-    .java
-    class ApplicationBuilder {
-      House build() {
-        return new House(
-                 new Kitchen(new Sink(), new Dishwasher(), new Refrigerator())
-               );
-      }
-    }
+    .python
+    class ApplicationBuilder(object):
+        def build(self):
+            house = Kitchen(Sink(),
+                            Dishwasher(),
+                            Refrigerator())
+            return house
 
 ---
 
-# Почему DI в питоне сильно меньше?
+# `.python __module__.__dict__.get("bar")(arg)`
 
 <!--
-- потому что в джаве он также необходим для тестирования
-  межмодульного взаимодействия (подмена this.foomodule)
-- (к сожалению) не так распространён => люди делают гадости
-- потому DI нужно внедрять и за модели, занимающиеся BL-вещами бить по
-  рукам
+- НЕТ!
+-->
+
+<img src="unit.png">
+
+---
+
+# Библиотека Mock
+
+<!--
+- уже в питоне 3.3
+-->
+
+    .python
+    import mock
+    class TestFoo(BaseTestCase):
+        @patch("path.to.module.query", return_value=0)
+        @patch("path.to.module.baz", return_value=0)
+        @patch("path.to.module.bar", return_value=0)
+        def test_should_return_0(self, bar_mock, baz_mock,
+                                 query_mock):
+             self.assertEquals(foo(0), 0)
+
+---
+
+# Я могу замокать практически всё -- но вы не хотите этого видеть
+
+<!--
+- держите отдельно модели, бизнес-логику, конструирование и выборку
+- за модели, занимающиеся BL-вещами бить по рукам
 -->
 
     .python
     class MyModel(models.Model):
         def save(self, *args, **kw):
             user = Users.objects.get(pk=10)
-            user.username = "because I can"
+            user.username = "I REGRET NOTHING"
             user.save()
             return super(MyModel, self).save(*args, **kw)
 
 
 ---
 
+# Mockstar -- declarative mocking
+
+<!--
+- обычно _test-файл -- для одного модуля (ppatch)
+- autospec=True
+- одно объявление сайд-эффектов для юнита (на все тесты)
+- можно добавлять возвращаемые по-умолчанию значения в side_effects
+- наконец, удобный доступ по `se.`
+-->
+
+    .python
+    import mockstar
+    ppatch = mockstar.prefixed_p("app.module.under.test",
+                                 autospec=True)
+    class TestFoo(BaseTestCase):
+        @ppatch('bar')
+        @ppatch('baz')
+        @ppatch('query')
+        def side_effects(self, se):
+            return self.invoke(se)
+
+        def test_should_return_0(self, se):
+            se.bar.return_value = 0
+            se.baz.return_value = 0
+            se.query.return_value = 0
+
+            self.assertEquals(foo(0), 0)
+
+---
+
+class: center, middle
+
+# Благодаря динамизму питона протестировать можно (почти) всё
+
+<!--
+- на любых уровнях
+- язык компактный и уютный
+- всё можно замокать
+-->
+
+---
+
 class: center, middle
 
 # Культура