Merge lp:~gandelman-a/ubuntu/precise/python-dingus/lp879051 into lp:ubuntu/precise/python-dingus

Proposed by Adam Gandelman
Status: Merged
Merged at revision: 4
Proposed branch: lp:~gandelman-a/ubuntu/precise/python-dingus/lp879051
Merge into: lp:ubuntu/precise/python-dingus
Diff against target: 1889 lines (+679/-863)
33 files modified
.pc/.version (+1/-0)
.pc/applied-patches (+1/-0)
.pc/debian-changes-0.3.2-1/dingus.egg-info/SOURCES.txt (+7/-0)
.pc/debian-changes-0.3.2-1/setup.py (+22/-0)
PKG-INFO (+119/-5)
README (+0/-76)
README.txt (+114/-0)
debian/changelog (+14/-0)
debian/control (+1/-1)
debian/docs (+1/-2)
debian/patches/debian-changes-0.3.2-1 (+64/-0)
debian/patches/series (+1/-0)
debian/source/format (+1/-0)
dingus.egg-info/PKG-INFO (+119/-5)
dingus.egg-info/SOURCES.txt (+2/-17)
dingus.py (+192/-57)
examples/__init__.py (+0/-1)
examples/googler/__init__.py (+0/-1)
examples/googler/googler.py (+0/-21)
examples/googler/runtests.py (+0/-12)
examples/googler/test_googler.py (+0/-56)
examples/urllib2/__init__.py (+0/-1)
examples/urllib2/runtests.py (+0/-12)
examples/urllib2/test_urllib2.py (+0/-67)
patches/series (+1/-0)
patches/setup_cleanup (+12/-0)
setup.py (+7/-5)
tests/runtests.py (+0/-14)
tests/test_call.py (+0/-22)
tests/test_call_list.py (+0/-113)
tests/test_dingus.py (+0/-290)
tests/test_dingus_test_case.py (+0/-65)
tests/test_exception_raiser.py (+0/-20)
To merge this branch: bzr merge lp:~gandelman-a/ubuntu/precise/python-dingus/lp879051
Reviewer Review Type Date Requested Status
Daniel Holbach (community) Approve
Ubuntu branches Pending
Review via email: mp+79992@code.launchpad.net

Description of the change

python-dingus (0.3.2-1ubuntu1) precise; urgency=low

  * Merge from debian testing (LP: #879051). Remaining changes:
    - dh_python2 transition

 -- Adam Gandelman <email address hidden> Thu, 20 Oct 2011 12:17:50 -0700

python-dingus (0.3.2-1) unstable; urgency=low

  * New upstream release
  * Update Standards version, no changes needed.

 -- David Watson <email address hidden> Sun, 07 Aug 2011 09:16:45 +0100

To post a comment you must log in.
Revision history for this message
Daniel Holbach (dholbach) wrote :

Good work! Thanks! Do you think you can forward the changes to Debian?

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory '.pc'
2=== added file '.pc/.version'
3--- .pc/.version 1970-01-01 00:00:00 +0000
4+++ .pc/.version 2011-10-20 19:36:43 +0000
5@@ -0,0 +1,1 @@
6+2
7
8=== added file '.pc/applied-patches'
9--- .pc/applied-patches 1970-01-01 00:00:00 +0000
10+++ .pc/applied-patches 2011-10-20 19:36:43 +0000
11@@ -0,0 +1,1 @@
12+debian-changes-0.3.2-1
13
14=== added directory '.pc/debian-changes-0.3.2-1'
15=== added directory '.pc/debian-changes-0.3.2-1/dingus.egg-info'
16=== added file '.pc/debian-changes-0.3.2-1/dingus.egg-info/SOURCES.txt'
17--- .pc/debian-changes-0.3.2-1/dingus.egg-info/SOURCES.txt 1970-01-01 00:00:00 +0000
18+++ .pc/debian-changes-0.3.2-1/dingus.egg-info/SOURCES.txt 2011-10-20 19:36:43 +0000
19@@ -0,0 +1,7 @@
20+README.txt
21+dingus.py
22+setup.py
23+dingus.egg-info/PKG-INFO
24+dingus.egg-info/SOURCES.txt
25+dingus.egg-info/dependency_links.txt
26+dingus.egg-info/top_level.txt
27\ No newline at end of file
28
29=== added directory '.pc/debian-changes-0.3.2-1/patches'
30=== added file '.pc/debian-changes-0.3.2-1/patches/series'
31=== added file '.pc/debian-changes-0.3.2-1/patches/setup_cleanup'
32=== added file '.pc/debian-changes-0.3.2-1/setup.py'
33--- .pc/debian-changes-0.3.2-1/setup.py 1970-01-01 00:00:00 +0000
34+++ .pc/debian-changes-0.3.2-1/setup.py 2011-10-20 19:36:43 +0000
35@@ -0,0 +1,22 @@
36+from setuptools import setup
37+
38+setup(name='dingus',
39+ version='0.3.2',
40+ description='A record-then-assert mocking library',
41+ long_description=file('README.txt').read(),
42+ author='Gary Bernhardt',
43+ author_email='gary.bernhardt@gmail.com',
44+ py_modules=['dingus'],
45+ data_files=[('', ['README.txt'])],
46+ license='MIT',
47+ url='https://github.com/garybernhardt/dingus',
48+ keywords='testing test mocking mock double stub fake record assert',
49+ classifiers=["Development Status :: 4 - Beta",
50+ "Intended Audience :: Developers",
51+ "License :: OSI Approved :: MIT License",
52+ "Operating System :: OS Independent",
53+ "Programming Language :: Python",
54+ "Topic :: Software Development :: Testing",
55+ ],
56+ )
57+
58
59=== modified file 'PKG-INFO'
60--- PKG-INFO 2009-10-22 11:00:55 +0000
61+++ PKG-INFO 2011-10-20 19:36:43 +0000
62@@ -1,15 +1,129 @@
63 Metadata-Version: 1.0
64 Name: dingus
65-Version: 0.1
66+Version: 0.3.2
67 Summary: A record-then-assert mocking library
68-Home-page: http://bitbucket.org/garybernhardt/dingus
69+Home-page: https://github.com/garybernhardt/dingus
70 Author: Gary Bernhardt
71 Author-email: gary.bernhardt@gmail.com
72 License: MIT
73-Description: UNKNOWN
74-Keywords: testing test mocking mock double stub fake
75+Description: ========
76+ DINGUSES
77+ ========
78+
79+ A dingus is sort of like a mock object. The main difference is that you don't
80+ set up expectations ahead of time. You just run your code, using a dingus in
81+ place of another object or class, and it will record what happens to it. Then,
82+ once your code has been exercised, you can make assertions about what it did
83+ to the dingus.
84+
85+ A new dingus is created from the Dingus class. You can give dinguses names,
86+ which helps with debugging your tests, especially when there are multiple
87+ dinguses in play.
88+
89+ >>> from dingus import Dingus
90+ >>> d = Dingus('root')
91+ >>> d
92+ <Dingus root>
93+
94+ Accessing any attribute of a dingus will return a new dingus.
95+
96+ >>> d.something
97+ <Dingus root.something>
98+
99+ There are a few exceptions for special dingus methods. We'll see some in a
100+ bit.
101+
102+ A dingus can also be called like a function or method. It doesn't care how
103+ many arguments you give it or what those arguments are. Calls to a dingus will
104+ always return the same object, regardless of the arguments.
105+
106+ >>> d()
107+ <Dingus root()>
108+ >>> d('argument')
109+ <Dingus root()>
110+ >>> d(55)
111+ <Dingus root()>
112+
113+ ========================
114+ RECORDING AND ASSERTIONS
115+ ========================
116+
117+ At any time we can get a list of calls that have been made to a dingus. Each
118+ entry in the call list contains:
119+
120+ * the name of the method called (or "()" if the dingus itself was called)
121+ * The arguments, or () if none
122+ * The keyword argumnets, or {} if none
123+ * The value that was returned to the caller
124+
125+ Here is a list of the calls we've made to d so far:
126+
127+ >>> from pprint import pprint
128+ >>> pprint(d.calls)
129+ [('()', (), {}, <Dingus root()>),
130+ ('()', ('argument',), {}, <Dingus root()>),
131+ ('()', (55,), {}, <Dingus root()>)]
132+
133+ You can filter calls by name, arguments, and keyword arguments:
134+
135+ >>> pprint(d.calls('()', 55))
136+ [('()', (55,), {}, <Dingus root()>)]
137+
138+ If you don't care about a particular argument's value, you can use the value
139+ DontCare when filtering:
140+
141+ >>> from dingus import DontCare
142+ >>> pprint(d.calls('()', DontCare))
143+ [('()', ('argument',), {}, <Dingus root()>),
144+ ('()', (55,), {}, <Dingus root()>)]
145+
146+ Dinguses can do more than just have attributes accessed and be called. They
147+ support many Python operators. The goal is to allow, and record, any
148+ interaction:
149+
150+ >>> d = Dingus('root')
151+ >>> (2 ** d.something)['hello']() / 100 * 'foo'
152+ <Dingus root.something.__rpow__[hello]().__div__.__mul__>
153+
154+ (Hopefully your real-world dingus recordings won't look like this!)
155+
156+ ========
157+ PATCHING
158+ ========
159+
160+ Dingus provides a context manager for patching objects during tests. For
161+ example:
162+
163+ >>> from dingus import patch
164+ >>> import urllib2
165+ >>> with patch('urllib2.urlopen'):
166+ ... print urllib2.urlopen.__class__
167+ <class 'dingus.Dingus'>
168+ >>> print urllib2.urlopen.__class__
169+ <type 'function'>
170+
171+ You can also use this as a decorator on your test methods:
172+
173+ >>> @patch('urllib2.urlopen')
174+ ... def test_something(self):
175+ ... pass
176+ ...
177+
178+ ===============
179+ DANGEROUS MAGIC
180+ ===============
181+
182+ Dingus can also automatically replace a module's globals when running tests.
183+ This allows you to write fully isolated unit tests. See
184+ examples/urllib2/test\_urllib2.py for an example. The author no longer
185+ recommends this feature, as it can encourage very brittle tests. You should
186+ feel the pain of manually mocking dependencies; the pain will tell you when a
187+ class collaborates with too many others.
188+
189+
190+Keywords: testing test mocking mock double stub fake record assert
191 Platform: UNKNOWN
192-Classifier: Development Status :: 2 - Pre-Alpha
193+Classifier: Development Status :: 4 - Beta
194 Classifier: Intended Audience :: Developers
195 Classifier: License :: OSI Approved :: MIT License
196 Classifier: Operating System :: OS Independent
197
198=== removed file 'README'
199--- README 2009-10-22 11:00:55 +0000
200+++ README 1970-01-01 00:00:00 +0000
201@@ -1,76 +0,0 @@
202-A dingus is sort of like a mock object. The main difference is that you don't
203-set up expectations ahead of time. You just run your code, using a dingus in
204-place of another object or class, and it will record what happens to it. Then,
205-once your code has been exercised, you can make assertions about what it did
206-to the dingus.
207-
208-A new dingus is created from the Dingus class. You can give dinguses names,
209-which helps with debugging your tests, especially when there are multiple
210-dinguses in play.
211-
212- >>> from dingus import Dingus
213- >>> d = Dingus('root')
214- >>> d
215- <Dingus root>
216-
217-Accessing any attribute of a dingus will return a new dingus.
218-
219- >>> d.something
220- <Dingus root.something>
221-
222-There are a few exceptions for special dingus methods. We'll see some in a
223-bit.
224-
225-A dingus can also be called like a function or method. It doesn't care how
226-many arguments you give it or what those arguments are. Calls to a dingus will
227-always return the same object, regardless of the arguments.
228-
229- >>> d()
230- <Dingus root()>
231- >>> d('argument')
232- <Dingus root()>
233- >>> d(55)
234- <Dingus root()>
235-
236-At any time we can get a list of calls that have been made to a dingus. Each
237-entry in the call list contains:
238- * the name of the method called (or "()" if the dingus itself was called)
239- * The arguments, or () if none
240- * The keyword argumnets, or {} if none
241- * The value that was returned to the caller
242-
243-Here is a list of the calls we've made to d so far:
244-
245- >>> from pprint import pprint
246- >>> pprint(d.calls)
247- [('()', (), {}, <Dingus root()>),
248- ('()', ('argument',), {}, <Dingus root()>),
249- ('()', (55,), {}, <Dingus root()>)]
250-
251-You can filter calls by name, arguments, and keyword arguments:
252-
253- >>> pprint(d.calls('()', 55))
254- [('()', (55,), {}, <Dingus root()>)]
255-
256-If you don't care about a particular argument's value, you can use the value
257-DontCare when filtering:
258-
259- >>> from dingus import DontCare
260- >>> pprint(d.calls('()', DontCare))
261- [('()', ('argument',), {}, <Dingus root()>),
262- ('()', (55,), {}, <Dingus root()>)]
263-
264-Dinguses can do more than just have attributes accessed and be called. They
265-support many Python operators. The goal is to allow, and record, any
266-interaction:
267-
268- >>> d = Dingus('root')
269- >>> (2 ** d.something)['hello']() / 100 * 'foo'
270- <Dingus root.something.__rpow__[hello]().__div__.__mul__>
271-
272-(Hopefully your real-world dingus recordings won't look like this!)
273-
274-Dingus can also automatically replace a module's globals when running tests.
275-This allows you to write fully isolated unit tests. See
276-examples/urllib2/test_urllib2.py for an example.
277-
278
279=== added file 'README.txt'
280--- README.txt 1970-01-01 00:00:00 +0000
281+++ README.txt 2011-10-20 19:36:43 +0000
282@@ -0,0 +1,114 @@
283+========
284+DINGUSES
285+========
286+
287+A dingus is sort of like a mock object. The main difference is that you don't
288+set up expectations ahead of time. You just run your code, using a dingus in
289+place of another object or class, and it will record what happens to it. Then,
290+once your code has been exercised, you can make assertions about what it did
291+to the dingus.
292+
293+A new dingus is created from the Dingus class. You can give dinguses names,
294+which helps with debugging your tests, especially when there are multiple
295+dinguses in play.
296+
297+ >>> from dingus import Dingus
298+ >>> d = Dingus('root')
299+ >>> d
300+ <Dingus root>
301+
302+Accessing any attribute of a dingus will return a new dingus.
303+
304+ >>> d.something
305+ <Dingus root.something>
306+
307+There are a few exceptions for special dingus methods. We'll see some in a
308+bit.
309+
310+A dingus can also be called like a function or method. It doesn't care how
311+many arguments you give it or what those arguments are. Calls to a dingus will
312+always return the same object, regardless of the arguments.
313+
314+ >>> d()
315+ <Dingus root()>
316+ >>> d('argument')
317+ <Dingus root()>
318+ >>> d(55)
319+ <Dingus root()>
320+
321+========================
322+RECORDING AND ASSERTIONS
323+========================
324+
325+At any time we can get a list of calls that have been made to a dingus. Each
326+entry in the call list contains:
327+
328+* the name of the method called (or "()" if the dingus itself was called)
329+* The arguments, or () if none
330+* The keyword argumnets, or {} if none
331+* The value that was returned to the caller
332+
333+Here is a list of the calls we've made to d so far:
334+
335+ >>> from pprint import pprint
336+ >>> pprint(d.calls)
337+ [('()', (), {}, <Dingus root()>),
338+ ('()', ('argument',), {}, <Dingus root()>),
339+ ('()', (55,), {}, <Dingus root()>)]
340+
341+You can filter calls by name, arguments, and keyword arguments:
342+
343+ >>> pprint(d.calls('()', 55))
344+ [('()', (55,), {}, <Dingus root()>)]
345+
346+If you don't care about a particular argument's value, you can use the value
347+DontCare when filtering:
348+
349+ >>> from dingus import DontCare
350+ >>> pprint(d.calls('()', DontCare))
351+ [('()', ('argument',), {}, <Dingus root()>),
352+ ('()', (55,), {}, <Dingus root()>)]
353+
354+Dinguses can do more than just have attributes accessed and be called. They
355+support many Python operators. The goal is to allow, and record, any
356+interaction:
357+
358+ >>> d = Dingus('root')
359+ >>> (2 ** d.something)['hello']() / 100 * 'foo'
360+ <Dingus root.something.__rpow__[hello]().__div__.__mul__>
361+
362+(Hopefully your real-world dingus recordings won't look like this!)
363+
364+========
365+PATCHING
366+========
367+
368+Dingus provides a context manager for patching objects during tests. For
369+example:
370+
371+ >>> from dingus import patch
372+ >>> import urllib2
373+ >>> with patch('urllib2.urlopen'):
374+ ... print urllib2.urlopen.__class__
375+ <class 'dingus.Dingus'>
376+ >>> print urllib2.urlopen.__class__
377+ <type 'function'>
378+
379+You can also use this as a decorator on your test methods:
380+
381+ >>> @patch('urllib2.urlopen')
382+ ... def test_something(self):
383+ ... pass
384+ ...
385+
386+===============
387+DANGEROUS MAGIC
388+===============
389+
390+Dingus can also automatically replace a module's globals when running tests.
391+This allows you to write fully isolated unit tests. See
392+examples/urllib2/test\_urllib2.py for an example. The author no longer
393+recommends this feature, as it can encourage very brittle tests. You should
394+feel the pain of manually mocking dependencies; the pain will tell you when a
395+class collaborates with too many others.
396+
397
398=== modified file 'debian/changelog'
399--- debian/changelog 2011-08-31 08:36:44 +0000
400+++ debian/changelog 2011-10-20 19:36:43 +0000
401@@ -1,3 +1,17 @@
402+python-dingus (0.3.2-1ubuntu1) precise; urgency=low
403+
404+ * Merge from debian testing (LP: #879051). Remaining changes:
405+ - dh_python2 transition
406+
407+ -- Adam Gandelman <adamg@canonical.com> Thu, 20 Oct 2011 12:17:50 -0700
408+
409+python-dingus (0.3.2-1) unstable; urgency=low
410+
411+ * New upstream release
412+ * Update Standards version, no changes needed.
413+
414+ -- David Watson <dwatson@debian.org> Sun, 07 Aug 2011 09:16:45 +0100
415+
416 python-dingus (0.1-2ubuntu1) oneiric; urgency=low
417
418 * dh_python2 transition.
419
420=== modified file 'debian/control'
421--- debian/control 2011-08-31 08:36:44 +0000
422+++ debian/control 2011-10-20 19:36:43 +0000
423@@ -11,7 +11,7 @@
424 Package: python-dingus
425 Architecture: all
426 Depends: ${misc:Depends}, ${python:Depends}
427-Description: A record-then-assert mocking library
428+Description: Record-then-assert mocking library
429 A dingus is sort of like a mock object. The main difference is that you don't
430 set up expectations ahead of time. You just run your code, using a dingus in
431 place of another object or class, and it will record what happens to it. Then,
432
433=== modified file 'debian/docs'
434--- debian/docs 2009-10-22 11:00:55 +0000
435+++ debian/docs 2011-10-20 19:36:43 +0000
436@@ -1,2 +1,1 @@
437-README
438-examples/
439+README.txt
440
441=== added directory 'debian/patches'
442=== added file 'debian/patches/debian-changes-0.3.2-1'
443--- debian/patches/debian-changes-0.3.2-1 1970-01-01 00:00:00 +0000
444+++ debian/patches/debian-changes-0.3.2-1 2011-10-20 19:36:43 +0000
445@@ -0,0 +1,64 @@
446+Description: Upstream changes introduced in version 0.3.2-1
447+ This patch has been created by dpkg-source during the package build.
448+ Here's the last changelog entry, hopefully it gives details on why
449+ those changes were made:
450+ .
451+ python-dingus (0.3.2-1) unstable; urgency=low
452+ .
453+ * New upstream release
454+ * Update Standards version, no changes needed.
455+ .
456+ The person named in the Author field signed this changelog entry.
457+Author: David Watson <dwatson@debian.org>
458+
459+---
460+The information above should follow the Patch Tagging Guidelines, please
461+checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here
462+are templates for supplementary fields that you might want to add:
463+
464+Origin: <vendor|upstream|other>, <url of original patch>
465+Bug: <url in upstream bugtracker>
466+Bug-Debian: http://bugs.debian.org/<bugnumber>
467+Bug-Ubuntu: https://launchpad.net/bugs/<bugnumber>
468+Forwarded: <no|not-needed|url proving that it has been forwarded>
469+Reviewed-By: <name and email of someone who approved the patch>
470+Last-Update: <YYYY-MM-DD>
471+
472+--- python-dingus-0.3.2.orig/setup.py
473++++ python-dingus-0.3.2/setup.py
474+@@ -7,7 +7,6 @@ setup(name='dingus',
475+ author='Gary Bernhardt',
476+ author_email='gary.bernhardt@gmail.com',
477+ py_modules=['dingus'],
478+- data_files=[('', ['README.txt'])],
479+ license='MIT',
480+ url='https://github.com/garybernhardt/dingus',
481+ keywords='testing test mocking mock double stub fake record assert',
482+--- python-dingus-0.3.2.orig/dingus.egg-info/SOURCES.txt
483++++ python-dingus-0.3.2/dingus.egg-info/SOURCES.txt
484+@@ -1,5 +1,6 @@
485+ README.txt
486+ dingus.py
487++setup.cfg
488+ setup.py
489+ dingus.egg-info/PKG-INFO
490+ dingus.egg-info/SOURCES.txt
491+--- /dev/null
492++++ python-dingus-0.3.2/patches/setup_cleanup
493+@@ -0,0 +1,12 @@
494++Index: python-dingus-0.3.2/setup.py
495++===================================================================
496++--- python-dingus-0.3.2.orig/setup.py 2011-08-07 09:37:57.900948735 +0100
497+++++ python-dingus-0.3.2/setup.py 2011-08-07 09:38:03.340954228 +0100
498++@@ -7,7 +7,6 @@
499++ author='Gary Bernhardt',
500++ author_email='gary.bernhardt@gmail.com',
501++ py_modules=['dingus'],
502++- data_files=[('', ['README.txt'])],
503++ license='MIT',
504++ url='https://github.com/garybernhardt/dingus',
505++ keywords='testing test mocking mock double stub fake record assert',
506+--- /dev/null
507++++ python-dingus-0.3.2/patches/series
508+@@ -0,0 +1 @@
509++setup_cleanup
510
511=== added file 'debian/patches/series'
512--- debian/patches/series 1970-01-01 00:00:00 +0000
513+++ debian/patches/series 2011-10-20 19:36:43 +0000
514@@ -0,0 +1,1 @@
515+debian-changes-0.3.2-1
516
517=== added directory 'debian/source'
518=== added file 'debian/source/format'
519--- debian/source/format 1970-01-01 00:00:00 +0000
520+++ debian/source/format 2011-10-20 19:36:43 +0000
521@@ -0,0 +1,1 @@
522+3.0 (quilt)
523
524=== modified file 'dingus.egg-info/PKG-INFO'
525--- dingus.egg-info/PKG-INFO 2009-10-22 11:00:55 +0000
526+++ dingus.egg-info/PKG-INFO 2011-10-20 19:36:43 +0000
527@@ -1,15 +1,129 @@
528 Metadata-Version: 1.0
529 Name: dingus
530-Version: 0.1
531+Version: 0.3.2
532 Summary: A record-then-assert mocking library
533-Home-page: http://bitbucket.org/garybernhardt/dingus
534+Home-page: https://github.com/garybernhardt/dingus
535 Author: Gary Bernhardt
536 Author-email: gary.bernhardt@gmail.com
537 License: MIT
538-Description: UNKNOWN
539-Keywords: testing test mocking mock double stub fake
540+Description: ========
541+ DINGUSES
542+ ========
543+
544+ A dingus is sort of like a mock object. The main difference is that you don't
545+ set up expectations ahead of time. You just run your code, using a dingus in
546+ place of another object or class, and it will record what happens to it. Then,
547+ once your code has been exercised, you can make assertions about what it did
548+ to the dingus.
549+
550+ A new dingus is created from the Dingus class. You can give dinguses names,
551+ which helps with debugging your tests, especially when there are multiple
552+ dinguses in play.
553+
554+ >>> from dingus import Dingus
555+ >>> d = Dingus('root')
556+ >>> d
557+ <Dingus root>
558+
559+ Accessing any attribute of a dingus will return a new dingus.
560+
561+ >>> d.something
562+ <Dingus root.something>
563+
564+ There are a few exceptions for special dingus methods. We'll see some in a
565+ bit.
566+
567+ A dingus can also be called like a function or method. It doesn't care how
568+ many arguments you give it or what those arguments are. Calls to a dingus will
569+ always return the same object, regardless of the arguments.
570+
571+ >>> d()
572+ <Dingus root()>
573+ >>> d('argument')
574+ <Dingus root()>
575+ >>> d(55)
576+ <Dingus root()>
577+
578+ ========================
579+ RECORDING AND ASSERTIONS
580+ ========================
581+
582+ At any time we can get a list of calls that have been made to a dingus. Each
583+ entry in the call list contains:
584+
585+ * the name of the method called (or "()" if the dingus itself was called)
586+ * The arguments, or () if none
587+ * The keyword argumnets, or {} if none
588+ * The value that was returned to the caller
589+
590+ Here is a list of the calls we've made to d so far:
591+
592+ >>> from pprint import pprint
593+ >>> pprint(d.calls)
594+ [('()', (), {}, <Dingus root()>),
595+ ('()', ('argument',), {}, <Dingus root()>),
596+ ('()', (55,), {}, <Dingus root()>)]
597+
598+ You can filter calls by name, arguments, and keyword arguments:
599+
600+ >>> pprint(d.calls('()', 55))
601+ [('()', (55,), {}, <Dingus root()>)]
602+
603+ If you don't care about a particular argument's value, you can use the value
604+ DontCare when filtering:
605+
606+ >>> from dingus import DontCare
607+ >>> pprint(d.calls('()', DontCare))
608+ [('()', ('argument',), {}, <Dingus root()>),
609+ ('()', (55,), {}, <Dingus root()>)]
610+
611+ Dinguses can do more than just have attributes accessed and be called. They
612+ support many Python operators. The goal is to allow, and record, any
613+ interaction:
614+
615+ >>> d = Dingus('root')
616+ >>> (2 ** d.something)['hello']() / 100 * 'foo'
617+ <Dingus root.something.__rpow__[hello]().__div__.__mul__>
618+
619+ (Hopefully your real-world dingus recordings won't look like this!)
620+
621+ ========
622+ PATCHING
623+ ========
624+
625+ Dingus provides a context manager for patching objects during tests. For
626+ example:
627+
628+ >>> from dingus import patch
629+ >>> import urllib2
630+ >>> with patch('urllib2.urlopen'):
631+ ... print urllib2.urlopen.__class__
632+ <class 'dingus.Dingus'>
633+ >>> print urllib2.urlopen.__class__
634+ <type 'function'>
635+
636+ You can also use this as a decorator on your test methods:
637+
638+ >>> @patch('urllib2.urlopen')
639+ ... def test_something(self):
640+ ... pass
641+ ...
642+
643+ ===============
644+ DANGEROUS MAGIC
645+ ===============
646+
647+ Dingus can also automatically replace a module's globals when running tests.
648+ This allows you to write fully isolated unit tests. See
649+ examples/urllib2/test\_urllib2.py for an example. The author no longer
650+ recommends this feature, as it can encourage very brittle tests. You should
651+ feel the pain of manually mocking dependencies; the pain will tell you when a
652+ class collaborates with too many others.
653+
654+
655+Keywords: testing test mocking mock double stub fake record assert
656 Platform: UNKNOWN
657-Classifier: Development Status :: 2 - Pre-Alpha
658+Classifier: Development Status :: 4 - Beta
659 Classifier: Intended Audience :: Developers
660 Classifier: License :: OSI Approved :: MIT License
661 Classifier: Operating System :: OS Independent
662
663=== modified file 'dingus.egg-info/SOURCES.txt'
664--- dingus.egg-info/SOURCES.txt 2009-10-22 11:00:55 +0000
665+++ dingus.egg-info/SOURCES.txt 2011-10-20 19:36:43 +0000
666@@ -1,23 +1,8 @@
667-README
668+README.txt
669 dingus.py
670 setup.cfg
671 setup.py
672 dingus.egg-info/PKG-INFO
673 dingus.egg-info/SOURCES.txt
674 dingus.egg-info/dependency_links.txt
675-dingus.egg-info/top_level.txt
676-examples/__init__.py
677-examples/googler/__init__.py
678-examples/googler/googler.py
679-examples/googler/runtests.py
680-examples/googler/test_googler.py
681-examples/urllib2/__init__.py
682-examples/urllib2/runtests.py
683-examples/urllib2/test_urllib2.py
684-tests/__init__.py
685-tests/runtests.py
686-tests/test_call.py
687-tests/test_call_list.py
688-tests/test_dingus.py
689-tests/test_dingus_test_case.py
690-tests/test_exception_raiser.py
691\ No newline at end of file
692+dingus.egg-info/top_level.txt
693\ No newline at end of file
694
695=== modified file 'dingus.py'
696--- dingus.py 2009-10-22 11:00:55 +0000
697+++ dingus.py 2011-10-20 19:36:43 +0000
698@@ -4,51 +4,50 @@
699
700
701 import sys
702-import new
703-
704-
705-def DingusTestCase(class_under_test):
706+from functools import wraps
707+
708+def DingusTestCase(object_under_test, exclude=None):
709+ if isinstance(exclude, basestring):
710+ raise ValueError("Strings not allowed for exclude. " +
711+ "Use a list: exclude=['identifier']")
712+ exclude = [] if exclude is None else exclude
713+
714+ def get_names_under_test():
715+ module = sys.modules[object_under_test.__module__]
716+ for name, value in module.__dict__.iteritems():
717+ if value is object_under_test or name in exclude:
718+ yield name
719+
720 class TestCase(object):
721 def setup(self):
722- module_name = class_under_test.__module__
723+ module_name = object_under_test.__module__
724 self._dingus_module = sys.modules[module_name]
725- self._dingus_replace_module_globals(self._dingus_module,
726- class_under_test)
727+ self._dingus_replace_module_globals(self._dingus_module)
728
729 def teardown(self):
730 self._dingus_restore_module(self._dingus_module)
731
732- def _dingus_wipe_module(self, module):
733+ def _dingus_replace_module_globals(self, module):
734 old_module_dict = module.__dict__.copy()
735- module.__dict__.clear()
736- module.__dict__.update(__builtins__)
737- module.__dict__['__dingused_dict__'] = old_module_dict
738-
739-
740- def _dingus_replace_module_globals(self, module, class_under_test):
741 module_keys = set(module.__dict__.iterkeys())
742- builtin_keys = set(__builtins__.iterkeys())
743
744- test_class_name = class_under_test.__name__
745- replaced_keys = (module_keys -
746- builtin_keys -
747- set([test_class_name]))
748- self._dingus_wipe_module(module)
749+ dunders = set(k for k in module_keys
750+ if k.startswith('__') and k.endswith('__'))
751+ replaced_keys = (module_keys - dunders - set(names_under_test))
752 for key in replaced_keys:
753 module.__dict__[key] = Dingus()
754- module.__dict__[test_class_name] = class_under_test
755+ module.__dict__['__dingused_dict__'] = old_module_dict
756
757 def _dingus_restore_module(self, module):
758 old_module_dict = module.__dict__['__dingused_dict__']
759 module.__dict__.clear()
760 module.__dict__.update(old_module_dict)
761
762-
763- TestCase.__name__ = '%sDingusTestCase' % class_under_test.__module__
764+ names_under_test = list(get_names_under_test())
765+ TestCase.__name__ = '%s_DingusTestCase' % '_'.join(names_under_test)
766 return TestCase
767
768
769-
770 # These sentinels are used for argument defaults because the user might want
771 # to pass in None, which is different in some cases than passing nothing.
772 class NoReturnValue(object):
773@@ -57,6 +56,65 @@
774 pass
775
776
777+def patch(object_path, new_object=NoArgument):
778+ module_name, attribute_name = object_path.rsplit('.', 1)
779+ return _Patcher(module_name, attribute_name, new_object)
780+
781+
782+class _Patcher:
783+ def __init__(self, module_name, attribute_name, new_object):
784+ self.module_name = module_name
785+ self.attribute_name = attribute_name
786+ self.module = _importer(self.module_name)
787+ if new_object is NoArgument:
788+ full_name = '%s.%s' % (module_name, attribute_name)
789+ self.new_object = Dingus(full_name)
790+ else:
791+ self.new_object = new_object
792+
793+ def __call__(self, fn):
794+ @wraps(fn)
795+ def new_fn(*args, **kwargs):
796+ self.patch_object()
797+ try:
798+ return fn(*args, **kwargs)
799+ finally:
800+ self.restore_object()
801+ return new_fn
802+
803+ def __enter__(self):
804+ self.patch_object()
805+
806+ def __exit__(self, exc_type, exc_value, traceback):
807+ self.restore_object()
808+
809+ def patch_object(self):
810+ self.original_object = getattr(self.module, self.attribute_name)
811+ setattr(self.module, self.attribute_name, self.new_object)
812+
813+ def restore_object(self):
814+ setattr(self.module, self.attribute_name, self.original_object)
815+
816+
817+def _importer(target):
818+ components = target.split('.')
819+ import_path = components.pop(0)
820+ thing = __import__(import_path)
821+
822+ for comp in components:
823+ import_path += ".%s" % comp
824+ thing = _dot_lookup(thing, comp, import_path)
825+ return thing
826+
827+
828+def _dot_lookup(thing, comp, import_path):
829+ try:
830+ return getattr(thing, comp)
831+ except AttributeError:
832+ __import__(import_path)
833+ return getattr(thing, comp)
834+
835+
836 class DontCare(object):
837 pass
838
839@@ -70,6 +128,9 @@
840 self.args = self[1]
841 self.kwargs = self[2]
842 self.return_value = self[3]
843+
844+ def __getnewargs__(self):
845+ return (self.name, self.args, self.kwargs, self.return_value)
846
847
848 class CallList(list):
849@@ -83,24 +144,41 @@
850 return all(args[i] in (DontCare, call.args[i])
851 for i in range(len(call.args)))
852
853+ @staticmethod
854+ def _match_kwargs(call, kwargs):
855+ if not kwargs:
856+ return True
857+ elif len(kwargs) != len(call.kwargs):
858+ return False
859+ else:
860+ return all(name in kwargs and kwargs[name] in (DontCare, val)
861+ for name, val in call.kwargs.iteritems())
862+
863 def one(self):
864 if len(self) == 1:
865 return self[0]
866 else:
867 return None
868
869- def __call__(self, name=NoArgument, *args, **kwargs):
870+ def once(self):
871+ return self.one()
872+
873+ def __call__(self, __name=NoArgument, *args, **kwargs):
874 return CallList([call for call in self
875- if (name is NoArgument or name == call.name)
876+ if (__name is NoArgument or __name == call.name)
877 and self._match_args(call, args)
878- and (not kwargs or kwargs == call.kwargs)])
879+ and self._match_kwargs(call, kwargs)])
880+
881+
882+def returner(return_value):
883+ return Dingus(return_value=return_value)
884
885
886 class Dingus(object):
887- def __init__(self, name=None, full_name=None, **kwargs):
888+ def __init__(self, dingus_name=None, full_name=None, **kwargs):
889 self._parent = None
890 self.reset()
891- name = 'dingus_%i' % id(self) if name is None else name
892+ name = 'dingus_%i' % id(self) if dingus_name is None else dingus_name
893 full_name = name if full_name is None else full_name
894 self._short_name = name
895 self._full_name = full_name
896@@ -108,7 +186,13 @@
897 self._full_name = full_name
898
899 for attr_name, attr_value in kwargs.iteritems():
900- setattr(self, attr_name, attr_value)
901+ if attr_name.endswith('__returns'):
902+ attr_name = attr_name.replace('__returns', '')
903+ returner = self._create_child(attr_name)
904+ returner.return_value = attr_value
905+ setattr(self, attr_name, returner)
906+ else:
907+ setattr(self, attr_name, attr_value)
908
909 self._replace_init_method()
910
911@@ -126,7 +210,7 @@
912 separator = ('' if (name.startswith('()') or name.startswith('['))
913 else '.')
914 full_name = self._full_name + separator + name
915- child = Dingus(name, full_name)
916+ child = self.__class__(name, full_name)
917 child._parent = self
918 return child
919
920@@ -146,9 +230,6 @@
921 return_value = property(_get_return_value, _set_return_value)
922
923 def __call__(self, *args, **kwargs):
924- if self.return_value is NoReturnValue:
925- self.return_value = self._create_child('()')
926-
927 self._log_call('()', args, kwargs, self.return_value)
928 if self._parent:
929 self._parent._log_call(self._short_name,
930@@ -162,50 +243,97 @@
931 self.calls.append(Call(name, args, kwargs, return_value))
932
933 def _should_ignore_attribute(self, name):
934- return name == '__pyobjc_object__'
935+ return name in ['__pyobjc_object__', '__getnewargs__']
936+
937+ def __getstate__(self):
938+ # Python cannot pickle a instancemethod
939+ # http://bugs.python.org/issue558238
940+ return [ (attr, value) for attr, value in self.__dict__.items() if attr != "__init__"]
941+
942+ def __setstate__(self, state):
943+ self.__dict__.update(state)
944+ self._replace_init_method()
945+
946+ def _existing_or_new_child(self, child_name, default_value=NoArgument):
947+ if child_name not in self._children:
948+ value = (self._create_child(child_name)
949+ if default_value is NoArgument
950+ else default_value)
951+ self._children[child_name] = value
952+
953+ return self._children[child_name]
954+
955+ def _remove_child_if_exists(self, child_name):
956+ if child_name in self._children:
957+ del self._children[child_name]
958
959 def __getattr__(self, name):
960 if self._should_ignore_attribute(name):
961 raise AttributeError(name)
962-
963- if name not in self._children:
964- self._children[name] = self._create_child(name)
965-
966- return self._children[name]
967+ return self._existing_or_new_child(name)
968+
969+ def __delattr__(self, name):
970+ self._log_call('__delattr__', (name,), {}, None)
971
972 def __getitem__(self, index):
973- child_name = '[%s]' % index
974- return_value = self._children.setdefault(
975- child_name, self._create_child('[%s]' % index))
976+ child_name = '[%s]' % (index,)
977+ return_value = self._existing_or_new_child(child_name)
978 self._log_call('__getitem__', (index,), {}, return_value)
979 return return_value
980
981 def __setitem__(self, index, value):
982- child_name = '[%s]' % index
983+ child_name = '[%s]' % (index,)
984 self._log_call('__setitem__', (index, value), {}, None)
985- self._children[child_name] = value
986+ self._remove_child_if_exists(child_name)
987+ self._existing_or_new_child(child_name, value)
988
989- def _create_operator(name):
990+ def _create_infix_operator(name):
991 def operator_fn(self, other):
992- if name not in self._children:
993- self._children[name] = self._create_child(name)
994- return self._children[name]
995+ return_value = self._existing_or_new_child(name)
996+ self._log_call(name, (other,), {}, return_value)
997+ return return_value
998 operator_fn.__name__ = name
999 return operator_fn
1000
1001- def _operators():
1002- operator_names = ['add', 'and', 'div', 'lshift', 'mod', 'mul', 'or',
1003- 'pow', 'rshift', 'sub', 'xor']
1004- reverse_operator_names = ['r%s' % name for name in operator_names]
1005- for operator_name in operator_names + reverse_operator_names:
1006+ _BASE_OPERATOR_NAMES = ['add', 'and', 'div', 'lshift', 'mod', 'mul', 'or',
1007+ 'pow', 'rshift', 'sub', 'xor']
1008+
1009+ def _infix_operator_names(base_operator_names):
1010+ # This function has to have base_operator_names passed in because
1011+ # Python's scoping rules prevent it from seeing the class-level
1012+ # _BASE_OPERATOR_NAMES.
1013+
1014+ reverse_operator_names = ['r%s' % name for name in base_operator_names]
1015+ for operator_name in base_operator_names + reverse_operator_names:
1016 operator_fn_name = '__%s__' % operator_name
1017 yield operator_fn_name
1018
1019- # Define each operator
1020- for operator_fn_name in _operators():
1021- exec('%s = _create_operator("%s")' % (operator_fn_name,
1022+ # Define each infix operator
1023+ for operator_fn_name in _infix_operator_names(_BASE_OPERATOR_NAMES):
1024+ exec('%s = _create_infix_operator("%s")' % (operator_fn_name,
1025 operator_fn_name))
1026
1027+ def _augmented_operator_names(base_operator_names):
1028+ # Augmented operators are things like +=. They behavior differently
1029+ # than normal infix operators because they return self instead of a
1030+ # new object.
1031+
1032+ return ['__i%s__' % operator_name
1033+ for operator_name in base_operator_names]
1034+
1035+ def _create_augmented_operator(name):
1036+ def operator_fn(self, other):
1037+ return_value = self
1038+ self._log_call(name, (other,), {}, return_value)
1039+ return return_value
1040+ operator_fn.__name__ = name
1041+ return operator_fn
1042+
1043+ # Define each augmenting operator
1044+ for operator_fn_name in _augmented_operator_names(_BASE_OPERATOR_NAMES):
1045+ exec('%s = _create_augmented_operator("%s")' % (operator_fn_name,
1046+ operator_fn_name))
1047+
1048 def __str__(self):
1049 return '<Dingus %s>' % self._full_name
1050 __repr__ = __str__
1051@@ -213,6 +341,13 @@
1052 def __len__(self):
1053 return 1
1054
1055+ def __iter__(self):
1056+ return iter([self._existing_or_new_child('__iter__')])
1057+
1058+ # We don't want to define __deepcopy__ at all. If there isn't one, deepcopy
1059+ # will clone the whole object, which is what we want.
1060+ __deepcopy__ = None
1061+
1062
1063 def exception_raiser(exception):
1064 def raise_exception(*args, **kwargs):
1065
1066=== removed directory 'examples'
1067=== removed file 'examples/__init__.py'
1068--- examples/__init__.py 2009-10-22 11:00:55 +0000
1069+++ examples/__init__.py 1970-01-01 00:00:00 +0000
1070@@ -1,1 +0,0 @@
1071-
1072
1073=== removed directory 'examples/googler'
1074=== removed file 'examples/googler/__init__.py'
1075--- examples/googler/__init__.py 2009-10-22 11:00:55 +0000
1076+++ examples/googler/__init__.py 1970-01-01 00:00:00 +0000
1077@@ -1,1 +0,0 @@
1078-
1079
1080=== removed file 'examples/googler/googler.py'
1081--- examples/googler/googler.py 2009-10-22 11:00:55 +0000
1082+++ examples/googler/googler.py 1970-01-01 00:00:00 +0000
1083@@ -1,21 +0,0 @@
1084-import urllib2
1085-
1086-
1087-class Googler(object):
1088- BASE_QUERY_URL = 'http://www.google.com/search?q='
1089- USER_AGENT = ('Mozilla/5.0' +
1090- '(X11; U; Linux i686; en-US; rv:1.7.8)' +
1091- 'Gecko/20050524 Fedora/1.5 Firefox/1.5')
1092-
1093- def __init__(self, terms):
1094- query_string = '+'.join(terms)
1095- request = urllib2.Request('%s%s' % (self.BASE_QUERY_URL,
1096- query_string))
1097- request.add_header('User-Agent', self.USER_AGENT)
1098- connection = urllib2.urlopen(request)
1099- self.response = connection.read()
1100-
1101-
1102-if __name__ == '__main__':
1103- print Googler(['lulz', 'megalulz']).response
1104-
1105
1106=== removed file 'examples/googler/runtests.py'
1107--- examples/googler/runtests.py 2009-10-22 11:00:55 +0000
1108+++ examples/googler/runtests.py 1970-01-01 00:00:00 +0000
1109@@ -1,12 +0,0 @@
1110-#!/usr/bin/env python
1111-
1112-import sys
1113-
1114-import nose
1115-
1116-
1117-if __name__ == '__main__':
1118- nose_args = sys.argv + [r'-m',
1119- r'((?:^|[b_.-])(:?[Tt]est|When|should))']
1120- nose.run(argv=nose_args)
1121-
1122
1123=== removed file 'examples/googler/test_googler.py'
1124--- examples/googler/test_googler.py 2009-10-22 11:00:55 +0000
1125+++ examples/googler/test_googler.py 1970-01-01 00:00:00 +0000
1126@@ -1,56 +0,0 @@
1127-from dingus import Dingus, DingusTestCase, DontCare
1128-import googler
1129-from googler import Googler
1130-
1131-
1132-class WhenCreatingRequest(DingusTestCase(Googler)):
1133- def setup(self):
1134- super(WhenCreatingRequest, self).setup()
1135- self.query_term = 'query term'
1136- self.googler = Googler([self.query_term])
1137-
1138- def should_use_google_search_url(self):
1139- query_url = 'http://www.google.com/search?q=%s' % self.query_term
1140- assert googler.urllib2.calls('Request', query_url)
1141-
1142-
1143-class WhenCreatingRequestWithTwoTerms(DingusTestCase(Googler)):
1144- def setup(self):
1145- super(WhenCreatingRequestWithTwoTerms, self).setup()
1146- self.terms = ['term1', 'term2']
1147- self.googler = Googler(self.terms)
1148-
1149- def should_combine_terms_in_search_url(self):
1150- query_string = '+'.join(self.terms)
1151- query_url = 'http://www.google.com/search?q=%s' % query_string
1152- assert googler.urllib2.calls('Request', query_url)
1153-
1154-
1155-class WhenSendingRequest(DingusTestCase(Googler)):
1156- def setup(self):
1157- super(WhenSendingRequest, self).setup()
1158- self.request = googler.urllib2.Request.return_value
1159- Googler(['query term'])
1160-
1161- def should_connect_to_google(self):
1162- assert googler.urllib2.calls('urlopen', self.request)
1163-
1164- def should_add_fake_user_agent_to_evade_filters(self):
1165- call = self.request.calls('add_header',
1166- 'User-Agent',
1167- DontCare).one()
1168- assert 'Mozilla' in call.args[1]
1169-
1170-
1171-class WhenConnected(DingusTestCase(Googler)):
1172- def setup(self):
1173- super(WhenConnected, self).setup()
1174- self.connection = googler.urllib2.urlopen.return_value
1175- self.googler = Googler(['query term'])
1176-
1177- def should_read_result(self):
1178- assert self.connection.calls('read')
1179-
1180- def should_store_result_in_attribute(self):
1181- assert self.googler.response is self.connection.read.return_value
1182-
1183
1184=== removed directory 'examples/urllib2'
1185=== removed file 'examples/urllib2/__init__.py'
1186--- examples/urllib2/__init__.py 2009-10-22 11:00:55 +0000
1187+++ examples/urllib2/__init__.py 1970-01-01 00:00:00 +0000
1188@@ -1,1 +0,0 @@
1189-
1190
1191=== removed file 'examples/urllib2/runtests.py'
1192--- examples/urllib2/runtests.py 2009-10-22 11:00:55 +0000
1193+++ examples/urllib2/runtests.py 1970-01-01 00:00:00 +0000
1194@@ -1,12 +0,0 @@
1195-#!/usr/bin/env python
1196-
1197-import sys
1198-
1199-import nose
1200-
1201-
1202-if __name__ == '__main__':
1203- nose_args = sys.argv + [r'-m',
1204- r'((?:^|[b_.-])(:?[Tt]est|When|should))']
1205- nose.run(argv=nose_args)
1206-
1207
1208=== removed file 'examples/urllib2/test_urllib2.py'
1209--- examples/urllib2/test_urllib2.py 2009-10-22 11:00:55 +0000
1210+++ examples/urllib2/test_urllib2.py 1970-01-01 00:00:00 +0000
1211@@ -1,67 +0,0 @@
1212-from dingus import DingusTestCase, DontCare
1213-import urllib2
1214-from urllib2 import urlopen
1215-
1216-
1217-# We want to unit test the urlopen function. It's very small (as you can see
1218-# by looking at the source at ${PYTHON_INSTALL}/lib/python2.5/urllib2.py
1219-#
1220-# (This example assumes Python 2.5. Hopefully it also works for your version.)
1221-#
1222-# Dingus allows us to test urlopen without actually touching the network, or
1223-# any other classes and functions at all. When we define the test class, we
1224-# inherit from "DingusTestCase(urlopen)". DingusTestCase will define setup and
1225-# teardown method that replace every object in urllib2 with a dingus
1226-# (except urlopen, which is the "object under test". It does this by looking
1227-# at the module that urlopen was defined in, making a backup copy of its
1228-# contents, then replacing everything with a dingus. So, instead of making
1229-# network connections as it usually would, urlopen will be making calls into
1230-# the dinguses, which will record the calls so that we can make assertions
1231-# about them later.
1232-class WhenOpeningURLs(DingusTestCase(urlopen)):
1233- def setup(self):
1234- # We have to call DingusTestCase's setup method so that it can
1235- # modify the urllib2 module's contents.
1236- super(WhenOpeningURLs, self).setup()
1237-
1238- # We set up the object under test here by calling urlopen with a URL.
1239- self.url = 'http://www.example.com'
1240- self.opened_url = urlopen(self.url)
1241-
1242- # First, we expect urlopen to try to open the URL.
1243- def should_open_provided_url(self):
1244- # Normally urlopen would use a prexisting "opener" object that would
1245- # touch the network, disk, etc., but DingusTestCase has replaced it
1246- # with a dingus. We first grab that _opener object so we can make
1247- # assertions about it.
1248- opener = urllib2._opener
1249-
1250- # We want to assert that urlopen should call "open" on the opener,
1251- # passing the URL we gave it. "open" also takes another argument, but
1252- # we don't care about that for this test. We pass in DontCare for
1253- # things we don't care about, and the dingus will ignore that argument
1254- # for the purposes of this assertion.
1255- assert opener.calls('open', self.url, DontCare).one()
1256-
1257- # Note that we never told the _opener dingus that it should have an
1258- # "open" method. A dingus has *all* methods - it will try to allow
1259- # anything to be done to it.
1260-
1261- def should_return_opened_url(self):
1262- # Now we want to assert that the opened object is returned to the
1263- # caller. The line of code from urllib2 that we're testing is:
1264- # return _opener.open(url, data)
1265- # We need to make sure that urlopen returned the result of that. We do
1266- # that by accessing _opener.open.return_value. _opener.open is the
1267- # dingus that replaced the original _opener.open method. The
1268- # return_value is a special attribute of all dinguses that gets
1269- # returned when the dingus is called as a function.
1270- assert self.opened_url is urllib2._opener.open.return_value
1271-
1272- # We could also define a teardown method for this test class, but that's
1273- # rarely needed when writing fully isolated tests like this one.
1274- # DingusTestCase does define a teardown method, though - it reverses the
1275- # changes it made to the module under test, removing the dinguses and
1276- # restoring the module's original contents. This class inherited it, so we
1277- # don't have to call it manually.
1278-
1279
1280=== added directory 'patches'
1281=== added file 'patches/series'
1282--- patches/series 1970-01-01 00:00:00 +0000
1283+++ patches/series 2011-10-20 19:36:43 +0000
1284@@ -0,0 +1,1 @@
1285+setup_cleanup
1286
1287=== added file 'patches/setup_cleanup'
1288--- patches/setup_cleanup 1970-01-01 00:00:00 +0000
1289+++ patches/setup_cleanup 2011-10-20 19:36:43 +0000
1290@@ -0,0 +1,12 @@
1291+Index: python-dingus-0.3.2/setup.py
1292+===================================================================
1293+--- python-dingus-0.3.2.orig/setup.py 2011-08-07 09:37:57.900948735 +0100
1294++++ python-dingus-0.3.2/setup.py 2011-08-07 09:38:03.340954228 +0100
1295+@@ -7,7 +7,6 @@
1296+ author='Gary Bernhardt',
1297+ author_email='gary.bernhardt@gmail.com',
1298+ py_modules=['dingus'],
1299+- data_files=[('', ['README.txt'])],
1300+ license='MIT',
1301+ url='https://github.com/garybernhardt/dingus',
1302+ keywords='testing test mocking mock double stub fake record assert',
1303
1304=== modified file 'setup.py'
1305--- setup.py 2009-10-22 11:00:55 +0000
1306+++ setup.py 2011-10-20 19:36:43 +0000
1307@@ -1,15 +1,16 @@
1308-from setuptools import setup, find_packages
1309+from setuptools import setup
1310
1311 setup(name='dingus',
1312- version='0.1',
1313+ version='0.3.2',
1314 description='A record-then-assert mocking library',
1315+ long_description=file('README.txt').read(),
1316 author='Gary Bernhardt',
1317 author_email='gary.bernhardt@gmail.com',
1318 py_modules=['dingus'],
1319 license='MIT',
1320- url='http://bitbucket.org/garybernhardt/dingus',
1321- keywords='testing test mocking mock double stub fake',
1322- classifiers=["Development Status :: 2 - Pre-Alpha",
1323+ url='https://github.com/garybernhardt/dingus',
1324+ keywords='testing test mocking mock double stub fake record assert',
1325+ classifiers=["Development Status :: 4 - Beta",
1326 "Intended Audience :: Developers",
1327 "License :: OSI Approved :: MIT License",
1328 "Operating System :: OS Independent",
1329@@ -17,3 +18,4 @@
1330 "Topic :: Software Development :: Testing",
1331 ],
1332 )
1333+
1334
1335=== removed directory 'tests'
1336=== removed file 'tests/__init__.py'
1337=== removed file 'tests/runtests.py'
1338--- tests/runtests.py 2009-10-22 11:00:55 +0000
1339+++ tests/runtests.py 1970-01-01 00:00:00 +0000
1340@@ -1,14 +0,0 @@
1341-#!/usr/bin/env python
1342-
1343-import sys
1344-
1345-import nose
1346-
1347-
1348-if __name__ == '__main__':
1349- nose_args = sys.argv + [r'-m',
1350- r'((?:^|[b_.-])(:?[Tt]est|When|should))',
1351- r'--with-doctest',
1352- r'--doctest-extension=']
1353- nose.run(argv=nose_args)
1354-
1355
1356=== removed file 'tests/test_call.py'
1357--- tests/test_call.py 2009-10-22 11:00:55 +0000
1358+++ tests/test_call.py 1970-01-01 00:00:00 +0000
1359@@ -1,22 +0,0 @@
1360-from dingus import Call
1361-
1362-
1363-class WhenInstantiated:
1364- def setup(self):
1365- self.call = Call('test name',
1366- 'test args',
1367- 'test kwargs',
1368- 'test return_value')
1369-
1370- def should_have_name(self):
1371- assert self.call.name == 'test name'
1372-
1373- def should_have_args(self):
1374- assert self.call.args == 'test args'
1375-
1376- def should_have_kwargs(self):
1377- assert self.call.kwargs == 'test kwargs'
1378-
1379- def should_have_return_value(self):
1380- assert self.call.return_value == 'test return_value'
1381-
1382
1383=== removed file 'tests/test_call_list.py'
1384--- tests/test_call_list.py 2009-10-22 11:00:55 +0000
1385+++ tests/test_call_list.py 1970-01-01 00:00:00 +0000
1386@@ -1,113 +0,0 @@
1387-from nose.tools import assert_raises
1388-
1389-from dingus import Call, CallList, DontCare
1390-
1391-
1392-class WhenEmpty:
1393- def setup(self):
1394- self.calls = CallList()
1395-
1396- def should_be_false_in_boolean_context(self):
1397- assert not self.calls
1398-
1399- def should_not_have_one_element(self):
1400- assert not self.calls.one()
1401-
1402-
1403-class WhenPopulatedWithACall:
1404- def setup(self):
1405- self.calls = CallList()
1406- self.calls.append(Call('test name',
1407- 'test args',
1408- 'test kwargs',
1409- 'test return_value'))
1410-
1411- def should_be_true_in_boolean_context(self):
1412- assert self.calls
1413-
1414- def should_have_exactly_one_call(self):
1415- assert self.calls.one()
1416-
1417- def should_not_return_call_when_querying_for_wrong_name(self):
1418- assert not self.calls('wrong name')
1419-
1420- def should_not_return_call_when_querying_for_wrong_args(self):
1421- assert not self.calls('test name', 'wrong args')
1422-
1423- def should_not_return_call_when_querying_for_wrong_kwargs(self):
1424- assert not self.calls('test name', wrong_key='wrong_value')
1425-
1426-
1427-class WhenPopulatedWithTwoCalls:
1428- def setup(self):
1429- self.calls = CallList()
1430- for _ in range(2):
1431- self.calls.append(Call('name', (), {}, None))
1432-
1433- def should_not_have_one_element(self):
1434- assert not self.calls.one()
1435-
1436-
1437-class WhenTwoCallsDifferByName:
1438- def setup(self):
1439- self.calls = CallList()
1440- self.calls.append(Call('name1', (), {}, None))
1441- self.calls.append(Call('name2', (), {}, None))
1442-
1443- def should_filter_on_name(self):
1444- assert self.calls('name1').one()
1445-
1446-
1447-class WhenTwoCallsDifferByArgs:
1448- def setup(self):
1449- self.calls = CallList()
1450- self.calls.append(Call('name', ('arg1',), {}, None))
1451- self.calls.append(Call('name', ('arg2',), {}, None))
1452-
1453- def should_filter_on_args(self):
1454- assert self.calls('name', 'arg1').one()
1455-
1456-
1457-class WhenCallsDifferInAllWays:
1458- def setup(self):
1459- self.calls = CallList()
1460- for name in ('name1', 'name2'):
1461- for args in (('arg1',), ('arg2',)):
1462- for kwargs in ({'kwarg1': 1}, {'kwarg2': 2}):
1463- call = Call(name, args, kwargs, 'return value')
1464- self.calls.append(call)
1465- self.call_count = len(self.calls)
1466-
1467- def should_filter_on_name(self):
1468- assert len(self.calls('name1')) == self.call_count / 2
1469-
1470- def should_filter_on_args(self):
1471- assert len(self.calls('name1', 'arg1')) == self.call_count / 4
1472-
1473- def should_filter_on_kwargs(self):
1474- assert len(self.calls('name1', kwarg1=1)) == self.call_count / 4
1475-
1476-
1477-class WhenCallsHaveMultipleArguments:
1478- def setup(self):
1479- self.calls = CallList()
1480- for arg1 in (1, 2):
1481- for arg2 in (1, 2):
1482- self.calls.append(Call('name',
1483- (arg1, arg2),
1484- {},
1485- 'return_value'))
1486- self.call_count = len(self.calls)
1487-
1488- def should_be_able_to_ignore_all_arguments(self):
1489- assert len(self.calls('name', DontCare, DontCare)) == self.call_count
1490-
1491- def should_be_able_to_ignore_first_argument(self):
1492- assert len(self.calls('name', 1, DontCare)) == self.call_count / 2
1493-
1494- def should_be_able_to_ignore_second_argument(self):
1495- assert len(self.calls('name', DontCare, 1)) == self.call_count / 2
1496-
1497- def should_be_able_to_specify_both_arguments(self):
1498- assert len(self.calls('name', 1, 1)) == self.call_count / 4
1499-
1500
1501=== removed file 'tests/test_dingus.py'
1502--- tests/test_dingus.py 2009-10-22 11:00:55 +0000
1503+++ tests/test_dingus.py 1970-01-01 00:00:00 +0000
1504@@ -1,290 +0,0 @@
1505-import operator
1506-from functools import partial
1507-
1508-from nose.tools import assert_raises
1509-
1510-from dingus import Dingus
1511-
1512-
1513-class WhenCreatingNewDingus:
1514- def setup(self):
1515- self.dingus = Dingus()
1516-
1517- def should_not_have_any_recorded_calls(self):
1518- assert not self.dingus.calls()
1519-
1520- def should_have_a_name(self):
1521- assert self.dingus.__name__ == 'dingus_%i' % id(self.dingus)
1522-
1523-
1524-class WhenCreatingNewDingusWithAName:
1525- def setup(self):
1526- self.dingus = Dingus('something')
1527-
1528- def should_have_a_name(self):
1529- assert self.dingus.__name__ == 'something'
1530-
1531- def should_include_name_in_repr(self):
1532- assert repr(self.dingus) == '<Dingus something>'
1533-
1534- def should_include_attribute_name_in_childrens_repr(self):
1535- assert repr(self.dingus.child) == '<Dingus something.child>'
1536-
1537- def should_include_attribute_name_in_repr_of_children_from_calling(self):
1538- assert repr(self.dingus()) == '<Dingus something()>'
1539-
1540- def should_include_attribute_name_in_repr_of_children_from_indexing(self):
1541- assert repr(self.dingus()['5']) == '<Dingus something()[5]>'
1542-
1543-
1544-class WhenCallingDingusAsFunction:
1545- def setup(self):
1546- self.dingus = Dingus()
1547- self.dingus('arg', kwarg=None)
1548-
1549- def should_record_call(self):
1550- assert self.dingus.calls()
1551-
1552- def should_have_exactly_one_call(self):
1553- assert self.dingus.calls().one()
1554-
1555- def should_record_args(self):
1556- assert self.dingus.calls.one().args == ('arg',)
1557-
1558- def should_record_kwargs(self):
1559- assert self.dingus.calls.one().kwargs == {'kwarg': None}
1560-
1561-
1562-class WhenCallingAttributeChild:
1563- def setup(self):
1564- self.parent = Dingus()
1565- self.child = self.parent.child
1566- self.child('arg', kwarg=None)
1567-
1568- def should_record_call_on_child(self):
1569- assert self.child.calls.one()
1570-
1571- def should_record_call_on_parent(self):
1572- assert self.parent.calls('child').one()
1573-
1574- def should_record_args(self):
1575- assert self.parent.calls('child').one().args == ('arg',)
1576-
1577- def should_record_kwargs(self):
1578- assert self.parent.calls('child').one().kwargs == {'kwarg': None}
1579-
1580-
1581-class WhenCallingAttributeGrandchild:
1582- def setup(self):
1583- self.grandparent = Dingus()
1584- self.parent = self.grandparent.parent
1585- self.child = self.parent.child
1586- self.child('arg', kwarg=None)
1587-
1588- def should_not_record_call_on_grandparent(self):
1589- assert not self.grandparent.calls('parent.child')
1590-
1591- def should_record_call_on_parent(self):
1592- assert self.parent.calls('child').one()
1593-
1594-
1595-class WhenCallingAttributesOfReturnedValues:
1596- def setup(self):
1597- self.grandparent = Dingus()
1598- self.parent = self.grandparent()
1599- self.child = self.parent.child
1600- self.child('arg', kwarg=None)
1601-
1602- def should_record_call_on_grandparent(self):
1603- assert self.grandparent.calls('()').one()
1604-
1605- def should_record_child_call_on_child(self):
1606- assert self.child.calls('()').one()
1607-
1608- def should_record_child_call_on_parent(self):
1609- assert self.parent.calls('child').one()
1610-
1611- def should_not_record_child_call_on_grandparent(self):
1612- assert not self.grandparent.calls('().child')
1613-
1614-
1615-class WhenCallingItemChild:
1616- def should_record_call(self):
1617- parent = Dingus()
1618- parent['child']()
1619- assert parent.calls('[child]').one()
1620-
1621-
1622-class WhenCallingListItemOfDingus:
1623- def setup(self):
1624- self.parent = Dingus()
1625- self.child = self.parent[0]
1626- self.child()
1627-
1628- def should_record_call_on_parent(self):
1629- assert self.parent.calls('[0]').one()
1630-
1631- def should_record_call_on_child(self):
1632- assert self.child.calls('()').one()
1633-
1634-
1635-class WhenAccessingMagicAttributes:
1636- def should_raise_attribute_error_for_pyobjc_object(self):
1637- # PyObjC uses __pyobjc_object__ to get an ObjC object from a Python
1638- # object. Returning a Mock will cause a crash.
1639- assert_raises(AttributeError, lambda: Dingus().__pyobjc_object__)
1640-
1641-
1642-class WhenApplyingBinaryOperators:
1643- operator_names = ['add', 'and_', 'div', 'lshift', 'mod', 'mul', 'or_',
1644- 'pow', 'rshift', 'sub', 'xor']
1645-
1646- def assert_returns_new_dingus(self, op):
1647- left, right = Dingus.many(2)
1648- result = op(left, right)
1649- assert result is not left and result is not right
1650-
1651- def should_always_return_new_dingus(self):
1652- for operator_name in self.operator_names:
1653- op = getattr(operator, operator_name)
1654- yield self.assert_returns_new_dingus, op
1655-
1656-
1657-class WhenComputingLength:
1658- def should_be_one(self):
1659- assert len(Dingus()) == 1
1660-
1661-
1662-class WhenAccessingReturnValueBeforeCalling:
1663- def setup(self):
1664- self.dingus = Dingus()
1665-
1666- def should_have_return_value_before_calling(self):
1667- assert self.dingus.return_value
1668-
1669- def should_return_same_return_value_that_existed_before_calling(self):
1670- original_return_value = self.dingus.return_value
1671- return_value = self.dingus()
1672- assert return_value is original_return_value
1673-
1674- def should_have_same_return_value_before_and_after_calling(self):
1675- original_return_value = self.dingus.return_value
1676- self.dingus()
1677- assert self.dingus.return_value is original_return_value
1678-
1679-
1680-class WhenSettingReturnValue:
1681- def setup(self):
1682- self.dingus = Dingus()
1683- self.return_value = 5
1684- self.dingus.return_value = self.return_value
1685-
1686- def should_return_assigned_return_value(self):
1687- assert self.dingus() is self.return_value
1688-
1689- def should_have_same_return_value_after_calling(self):
1690- self.dingus()
1691- assert self.dingus.return_value is self.return_value
1692-
1693-
1694-class WhenSettingAttributes:
1695- def setup(self):
1696- self.dingus = Dingus()
1697- self.attr = Dingus()
1698- self.dingus.attr = self.attr
1699-
1700- def should_remember_attr(self):
1701- assert self.dingus.attr is self.attr
1702-
1703- def should_not_return_attributes_as_items(self):
1704- assert self.dingus['attr'] is not self.attr
1705-
1706- def should_return_distinct_dinguses_for_different_attributes(self):
1707- assert self.dingus['attr'] is not self.dingus['attr2']
1708-
1709-
1710-class WhenAccessingItems:
1711- def should_log_access(self):
1712- dingus = Dingus()
1713- dingus['item']
1714- assert dingus.calls('__getitem__', 'item').one()
1715-
1716- def should_log_access_after_initial_item_read(self):
1717- dingus = Dingus()
1718- for _ in range(2):
1719- dingus['item']
1720- assert len(dingus.calls('__getitem__', 'item')) == 2
1721-
1722-
1723-class WhenSettingItems:
1724- def setup(self):
1725- self.dingus = Dingus()
1726- self.item = Dingus()
1727- self.dingus['item'] = self.item
1728-
1729- def should_remember_item(self):
1730- assert self.dingus['item'] is self.item
1731-
1732- def should_log_access(self):
1733- assert self.dingus.calls('__setitem__', 'item', self.item).one()
1734-
1735- def should_not_return_items_as_attributes(self):
1736- assert self.dingus.item is not self.item
1737-
1738- def should_return_distinct_dinguses_for_different_items(self):
1739- assert self.dingus['item'] is not self.dingus['item2']
1740-
1741-
1742-class WhenNothingIsSet:
1743- def setup(self):
1744- self.attribute_name = 'attr'
1745- self.dingus = Dingus()
1746-
1747- def should_be_able_to_access_attributes_that_dont_exist(self):
1748- assert isinstance(getattr(self.dingus, self.attribute_name), Dingus)
1749-
1750- def should_get_same_attribute_on_every_access(self):
1751- get_attr = lambda: getattr(self.dingus, self.attribute_name)
1752- assert get_attr() is get_attr()
1753-
1754- def should_be_able_to_acces_items_that_dont_exist(self):
1755- assert isinstance(self.dingus[self.attribute_name], Dingus)
1756-
1757- def should_get_same_item_on_every_access(self):
1758- get_item = lambda: self.dingus[self.attribute_name]
1759- assert get_item() is get_item()
1760-
1761- def should_have_attributes_that_have_not_been_set(self):
1762- assert hasattr(self.dingus, self.attribute_name)
1763-
1764-
1765-class WhenSpecifyingAttributesViaKeywordArguments:
1766- def should_set_specified_attributes(self):
1767- attr = Dingus()
1768- object_with_attr = Dingus(attr=attr)
1769- assert object_with_attr.attr is attr
1770-
1771-
1772-class WhenCallingInitMethod:
1773- def should_record_call(self):
1774- dingus = Dingus()
1775- dingus.__init__()
1776- assert dingus.calls('__init__').one()
1777-
1778-
1779-class WhenCreatingMultipleDinguses:
1780- def should_return_a_dingus_when_asked_for_one(self):
1781- assert len(Dingus.many(1)) == 1
1782-
1783- def should_return_two_dinguses_when_asked_for_two(self):
1784- assert len(Dingus.many(2)) == 2
1785-
1786- def should_return_dingus_instances_when_asked_for_multiple(self):
1787- assert all(isinstance(dingus, Dingus) for dingus in Dingus.many(2))
1788-
1789- def should_return_dinguses_in_tuple(self):
1790- assert isinstance(Dingus.many(2), tuple)
1791-
1792- def should_return_nothing_when_asked_for_zero_dinguses(self):
1793- assert not Dingus.many(0)
1794-
1795
1796=== removed file 'tests/test_dingus_test_case.py'
1797--- tests/test_dingus_test_case.py 2009-10-22 11:00:55 +0000
1798+++ tests/test_dingus_test_case.py 1970-01-01 00:00:00 +0000
1799@@ -1,65 +0,0 @@
1800-import sys
1801-import new
1802-
1803-from dingus import DingusTestCase, Dingus
1804-
1805-
1806-class WhenRunningTestCaseOnAModule(object):
1807- def setup(self):
1808- self.module = self._create_fake_module()
1809- self.class_under_test = self._create_class_under_test()
1810- self.test_case_class = self._create_test_case_class()
1811-
1812- def _create_fake_module(self):
1813- module = new.module('test_module')
1814- module.value = 'value'
1815- sys.modules[module.__name__] = module
1816- return module
1817-
1818- def _create_class_under_test(self):
1819- class ClassThatWouldBeUnderTest:
1820- pass
1821- ClassThatWouldBeUnderTest.__module__ = self.module.__name__
1822- self.module.ClassThatWouldBeUnderTest = ClassThatWouldBeUnderTest
1823- return ClassThatWouldBeUnderTest
1824-
1825- def _create_test_case_class(self):
1826- class TestCaseClass(DingusTestCase(self.class_under_test)):
1827- pass
1828- return TestCaseClass
1829-
1830- def teardown(self):
1831- del sys.modules[self.module.__name__]
1832-
1833-
1834-class WhenCallingSetupFunction(WhenRunningTestCaseOnAModule):
1835- def setup(self):
1836- super(WhenCallingSetupFunction, self).setup()
1837- self.test_case_instance = self.test_case_class()
1838- self.test_case_instance.setup()
1839-
1840- def teardown(self):
1841- self.test_case_instance.teardown()
1842- super(WhenCallingSetupFunction, self).teardown()
1843-
1844- def should_replace_module_attributes(self):
1845- assert isinstance(self.module.value, Dingus)
1846-
1847- def should_leave_class_under_test_intact(self):
1848- assert self.module.ClassThatWouldBeUnderTest is self.class_under_test
1849-
1850-
1851-class WhenCallingTeardownFunction(WhenRunningTestCaseOnAModule):
1852- def setup(self):
1853- super(WhenCallingTeardownFunction, self).setup()
1854- self.test_case_object = self.test_case_class()
1855- self.original_module_dict = self.module.__dict__.copy()
1856- self.test_case_object.setup()
1857- self.test_case_object.teardown()
1858-
1859- def should_restore_module_attributes(self):
1860- assert self.module.value is 'value'
1861-
1862- def should_leave_globals_as_they_were_before_dingusing(self):
1863- assert self.module.__dict__ == self.original_module_dict
1864-
1865
1866=== removed file 'tests/test_exception_raiser.py'
1867--- tests/test_exception_raiser.py 2009-10-22 11:00:55 +0000
1868+++ tests/test_exception_raiser.py 1970-01-01 00:00:00 +0000
1869@@ -1,20 +0,0 @@
1870-from nose.tools import assert_raises
1871-from nose.tools import raises
1872-
1873-from dingus import exception_raiser
1874-
1875-
1876-class WhenCalled:
1877- def setup(self):
1878- exception = ValueError()
1879- self.raise_exception = exception_raiser(exception)
1880-
1881- def should_raise_provided_exception(self):
1882- assert_raises(ValueError, self.raise_exception)
1883-
1884- def should_take_args(self):
1885- assert_raises(ValueError, self.raise_exception, 1)
1886-
1887- def should_take_kwargs(self):
1888- assert_raises(ValueError, self.raise_exception, kwarg=1)
1889-

Subscribers

People subscribed via source and target branches