測試可以簡單一點嗎?

Presenter Notes

Hubert (@yhchan)
趨勢科技

Presenter Notes

寫 code 很開心,but

Presenter Notes

人生最厲害就是這個 BUT!

Presenter Notes

Python 很多版本

Presenter Notes

還有 PyPy

Presenter Notes

以 Celery 為例

Celery version 3.0 runs on,

  • Python (2.5, 2.6, 2.7, 3.2, 3.3)
  • PyPy (1.8, 1.9)
  • Jython (2.5, 2.7).

Presenter Notes

Python 2 -> Python 3
刺激!

Presenter Notes

怎麼支援多個版本的 Python?

Presenter Notes

目前我們的狀況

Presenter Notes

開發機 Ubuntu 12.04.2 + Python 2.7
實際上線 CentOS 5.x + Python 2.6

Presenter Notes

Python 2.6 v.s Python 2.7

Presenter Notes

語法不一樣

Python 2.7+ Syntax

1 # Python 2.7+ Dictionary Comprehension
2 map = {'a': 1, 'b': 2}
3 inv_map {v: k for k, v in map.items()}
4 
5 # Python 2.7+ with statements
6 with open("out.txt","wt"), open("in.txt") as file_out, file_in:
7     pass

Presenter Notes

寫 Unit Test 也會遇到...

Python 2.7+ Assertions

1 # Python 2.7+ contains more assertions
2 items1 = {'product': 'Worry-Free'}
3 items2 = {'product': 'OfficeScan'}
4 self.assertDictEqual(items1, items2)

Presenter Notes

跨版本的解決方案

Presenter Notes

Travis CI

Presenter Notes

Celery .travis.yml

1 language: python
2 python:
3     - 2.6
4     - 2.7
5     - 3.2
6     - 3.3
7 install:
8     - pip install --use-mirrors tox
9 script: TOXENV=py$(echo $TRAVIS_PYTHON_VERSION | tr -d .) tox -v

Presenter Notes

無痛與 Github 整合

Presenter Notes

但是其他 Private Repo 呢...

Presenter Notes

Jenkins

Presenter Notes

怎麼跨版本測試?

Presenter Notes

今天的主題 tox

Presenter Notes

tox 能做什麼

tox 是基於 virtualenv 的測試工具

  • 檢測專案是否正確安裝在不同的 python 環境
  • 在不同 python 環境執行單元測試
  • 開發者環境就可以執行

Presenter Notes

設定:tox.ini

1 [tox]
2 envlist = py26,py27
3 
4 [testenv]
5 deps=pytest
6 commands=py.test

Presenter Notes

感覺跟 Travis CI 類似,但是

Presenter Notes

setup.py gotcha

Babel setup.py cmdclass integration

 1 from distutils.core import setup
 2 from babel.messages import frontend as babel
 3 
 4 setup(
 5     ...
 6     cmdclass={'compile_catalog': babel.compile_catalog,
 7               'extract_messages': babel.extract_messages,
 8               'init_catalog': babel.init_catalog,
 9               'update_catalog': babel.update_catalog}
10 )

Presenter Notes

Bang!

GLOB sdist-make: /Users/hubert/tmp/samplepy/setup.py
...
msg=packaging
cmdargs=['/Users/hubert/py-envs/py27env/bin/python', 
         local('/Users/hubert/tmp/samplepy/setup.py'), 'sdist', 
         '--formats=zip', '--dist-dir', 
         local('/Users/hubert/tmp/samplepy/.tox/dist')]
env=None
Traceback (most recent call last):
  File "setup.py", line 2, in <module>
      from babel.messages import frontend as babel
      ImportError: No module named babel.messages

Presenter Notes

ImportError: No module named babel.messages

Presenter Notes

就算指定 deps 也沒用

Presenter Notes

不能對 virtualenv 客製

Presenter Notes

另外一個我們遇到的麻煩

Presenter Notes

緣由:Close Source

Presenter Notes

不能用 public pypi
加上沒有 local pypi server

Presenter Notes

之前根本沒人管 dependency ...

Presenter Notes

install_requires
dependency_links

Presenter Notes

第一次嘗試

Presenter Notes

從 tarball 來

1 from distutils.core import setup
2 setup(
3     ...
4     dependency_links=[
5         'http://github.com/celery/celery/tarball/master#egg=celery'
6     ]
7 )

Presenter Notes

gitlab 下載 tarball 不能指定 branch

Presenter Notes

setup.py 一定要在第一層

Presenter Notes

喝咖啡 ☕

Presenter Notes

第二次嘗試

Presenter Notes

從 SCM 來

1 from distutils.core import setup
2 setup(
3     ...
4     dependency_links=[
5         'git+https://example.com/spamneggs/foobar.git#egg=foobar-1.2.3'
6     ]
7 )

Presenter Notes

setup.py 一定要在第一層...

Presenter Notes

心中下了場雪 ☃

Presenter Notes

第三次嘗試
git submodules + file:

Presenter Notes

git submodules + file:

1 from distutils.core import setup
2 setup(
3     ...
4     dependency_links=[
5         'file:../../../deps/foobar#foobar'
6     ]
7 )

Presenter Notes

裝起來了☺

Presenter Notes

但是 tox 炸了 ☹
python setup.py sdist

Presenter Notes

老實說:我們的設計不良

Presenter Notes

tox 還是很好用

Presenter Notes

Continuous Integration

Presenter Notes

測試 + Flake8 + Coverage

 1 [tox]
 2 envlist = py26, py27
 3 
 4 [testenv]
 5 commands = nosetests {posargs:--with-cov --cov-report=xml --with-xunit --cov package}
 6 flake8 --exit-zero package
 7 
 8 deps = nose
 9 nose-cov
10 coverage
11 mock
12 flake8
13 
14 [testenv:py26]
15 basepython={homedir}/.pythonbrew/pythons/Python-2.6.8/bin/python
16 
17 [testenv:py27]
18 basepython={homedir}/.pythonbrew/pythons/Python-2.7.5/bin/python

Presenter Notes

pythonbrew / pythonz
給你不同版本的 Python

Presenter Notes

Jenkins Matrix Project

Jenkins-Matrix

Presenter Notes

我覺得不順手的地方

  • deps 有修改就會重建 virtualenv,不能新增就好嗎?
  • 沒有 Travis CI 的 install 區塊

Presenter Notes

tox ☀

Presenter Notes

換個主題

Presenter Notes

測試的起手式

Presenter Notes

import unittest

Presenter Notes

assertEqual

Presenter Notes

Python 2.6 的世界有點不方便

Presenter Notes

即使是簡單比較

Code

1 def test_simple_dict_compare_1(self):
2     dict1 = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
3     dict2 = {'a': 1, 'b': 1, 'c': 3, 'd': 5}
4 
5     self.assertEqual(dict1, dict2)

Result

AssertionError: {'a': 1, 'c': 3, 'b': 2, 'd': 4} != 
                {'a': 1, 'c': 3, 'b': 1, 'd': 5}

Presenter Notes

Assertion 看不懂也枉然

Presenter Notes

import testfixtures

Presenter Notes

compare

Presenter Notes

compare 好讀多了

Code

1 def test_simple_dict_compare_2(self):
2     dict1 = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
3     dict2 = {'a': 1, 'b': 1, 'c': 3, 'd': 5}
4 
5     compare(dict1, dict2)

Result

AssertionError: dict not as expected:

same:
['a', 'c']

values differ:
'b': 2 != 1
'd': 4 != 5

Presenter Notes

compare 遇到 JSON

Result

same:
[u'completed_in', u'max_id_str', u'next_page', u'page']]

values differ:
u'max_id': 122078461840982020 != 122078461840982021
u'results': [{u'created_at': u'Thu, 06 Oct 2011 19:36:17 +0000',
  u'entities': {u'urls': [{u'display_url': u'bit.ly/q9fyz9',
                           u'expanded_url': u'http://bit.ly/q9fyz9',
                           u'indices': [37, 57],
                           u'url': u'http://t.co/L9JXJ2ee'}]},
...
以下三千行

Presenter Notes

list 只要有一個不一樣就...

Presenter Notes

Python 2.7 改善很多

把差異的部份特別標示出來

                 u'geo': None,
-                u'id': 122033350327279620,
?                                        ^

+                u'id': 122033350327279621,
?

Presenter Notes

Python 2.6 你可以
pip install unittest2

Presenter Notes

compare 還不夠好,但是也還堪用

Presenter Notes

Mocking

Presenter Notes

Replacer

Presenter Notes

以前我們的會這樣寫

1 @patch('hello.yoyo.ClassB')
2 @patch('hello.yoyo.ClassA')
3 def test_hello(self, MockClassA, MockClassB):
4     pass

Presenter Notes

但是每 mock 一個就多一個 parameter ...

Presenter Notes

後來我們改成這樣

 1 def test_mock(self):
 2     # python 2.6, or using contextlib
 3     with patch('test_hello.RealClassA') as mock_a:
 4         with patch('test_hello.RealClassB') as mock_b:
 5             pass
 6 
 7 def test_mock_27(self):
 8     # python 2.7+
 9     with patch('test_hello.RealClassA') as mock_a, \
10         with patch('test_hello.RealClassB') as mock_b:
11 
12         pass

Presenter Notes

PEP20: Flat is better than nested

Presenter Notes

Replacer

1 with Replacer() as r:
2     r.replace('test_hello.RealClassA', MagicMock())
3     r.replace('test_hello.RealClassB', MagicMock())
4 
5     instance_a = RealClassA()
6     instance_b = RealClassB()

Presenter Notes

xUnit with patch

 1 def setUp(self):
 2     self.patcher_a = patch('test_hello.RealClassA', spec=True)
 3     self.patcher_b = patch('test_hello.RealClassB', spec=True)
 4 
 5     self.mock_a = self.patcher_a.start()
 6     self.mock_b = self.patcher_b.start()
 7 
 8 def tearDown(self):
 9     self.patcher_a.stop()
10     self.patcher_b.stop()

Presenter Notes

tearDown 會不會漏?

Presenter Notes

xUnit with Replacer

 1 def setUp(self):
 2     self.replacer = Replacer()
 3 
 4     self.mock_a = MagicMock()
 5     self.mock_b = MagicMock()
 6 
 7     self.replacer.replace('test_hello.RealClassA', self.mock_a)
 8     self.replacer.replace('test_hello.RealClassB', self.mock_b)
 9 
10 def tearDown(self):
11     self.replacer.restore()

Presenter Notes

testfixtures 其他的好東西

Presenter Notes

TempDirectory 超好用

Presenter Notes

ShouldRaises
2.7 之後就還好

Presenter Notes

時間有限,想說的很多

Presenter Notes

謝謝 ☺

Presenter Notes

Q & A

Presenter Notes