Testing Blog

TotT: Better Stubbing in Python

Wednesday, January 24, 2007
Share on Twitter Share on Facebook
Google
Labels: TotT

22 comments :

  1. WilliamJanuary 25, 2007 at 12:13:00 AM PST

    Interesting technique. I think the article will be improved if it also mentioned that the technique comes with a cost of adding an extra unintuitive parameter into your code base. All subsequent maintainers of the code will probably find it very confusing to see that the path_checker is a parameter which makes the codebase less maintainable.

    ReplyDelete
    Replies
      Reply
  2. mnotJanuary 25, 2007 at 12:26:00 AM PST

    Is that really any better? Now the function signature is polluted with arguments for everything that I might want to use this technique for -- which could be considerable.

    ReplyDelete
    Replies
      Reply
  3. hfuecksJanuary 25, 2007 at 12:45:00 AM PST

    Think pretty much anything in this area quickly turns into an ugly hack but modifying Foo's parameters is probably worse than others.

    A transparent solution can be done via inspecting the stack and checking who the caller is (via python's inspect module) - if it's the function you want to test, using the dummy os.path.exists, otherwise use the real one. Would provide an example but whitespace formatting not supported the the comments here...

    That said surely DoSomething and DoSomethingElse should be stubbed out as well? It's only Foo being tested ;)

    ReplyDelete
    Replies
      Reply
  4. DanielJanuary 25, 2007 at 2:20:00 AM PST

    Another possibility is to make the stub check the argument, return a value for 'bar' and call the original function for anything else.

    os.path.exists = lambda x: x == "bar" or old_exists(x)

    Sorry if my code isn't much cop, I don't use Python much.

    ReplyDelete
    Replies
      Reply
  5. Roger BrowneJanuary 25, 2007 at 4:56:00 AM PST

    Cool!

    I don't use python much, but I support the technique of accepting stubbing as a worthwhile evil and designing it into the code. It's not really "polluting" the interface" because it IS the interface.

    ReplyDelete
    Replies
      Reply
  6. My notesJanuary 25, 2007 at 5:52:00 AM PST

    Hey,

    How about posting an RSS feed for the PDFs? I could hook it up via a script to print them directly to my printer at the office :)

    Cheers

    ReplyDelete
    Replies
      Reply
  7. UnknownJanuary 25, 2007 at 10:08:00 AM PST

    Is Foo just a helper method for your unit test class? If so, I can see value in the new parameter but if not, it does seem like a hack.

    Full disclosure: I'm pretty new to Python so maybe I'm missing something.

    ReplyDelete
    Replies
      Reply
  8. Nick ParkerJanuary 25, 2007 at 10:46:00 AM PST

    This comment has been removed by the author.

    ReplyDelete
    Replies
      Reply
  9. Nick ParkerJanuary 25, 2007 at 10:47:00 AM PST

    This comment has been removed by the author.

    ReplyDelete
    Replies
      Reply
  10. Nick ParkerJanuary 25, 2007 at 10:50:00 AM PST

    What about extracting the os.path.exists into a helper that is injected at construction of the class containing Foo? This way your not polluting the parameter list but still providing a separation of concerns.

    ReplyDelete
    Replies
      Reply
  11. Ian BickingJanuary 25, 2007 at 2:12:00 PM PST

    I think this issue -- at least the particular one you give -- is a sign of a smell further up in the stack. If you really are doing lots of file operations, you should really write files. The stubbing and mocking you'll have to do to avoid it just isn't worth it, and automatically wiping your scratch test files before the test runs is easy enough as well.

    Once you are comfortable with the testing of the file-related operations, you shouldn't keep testing them. Either stub those out, or just keep using scratch areas for the files.

    Similarly, if you rely on some other external service (the service in this case being the filesystem), putting in a trivial service is often better than futzing with replacing functions that are otherwise quite sufficient. A good example of a tool that allows this is wsgi_intercept, which lets you attach pretend HTTP apps to arbitrary host names. Of course, that's basically the original technique that you wanted to avoid; that someone else did it in a library kind of makes it okay. If you were mocking something that wasn't yet mocked, I'd recommend something more like your technique.

    Putting in a trivial service implementation should just be a matter of configuration. Configuring an address to http://localhost, or configuring the base file path, or pointing to a fake smtp server. You have to do that anyway regardless of testing, and that's a good place for your mocking.

    ReplyDelete
    Replies
      Reply
  12. maciejJanuary 25, 2007 at 4:51:00 PM PST

    I hope this goes without saying, but in production code the filesystem should be considered untrusted and an assertion about its contents would never be valid. Instead all possible failures should be handled. (Unless you *are* the filesystem.) I am assuming this is just for the sake of example though.

    ReplyDelete
    Replies
      Reply
  13. Marius GedminasJanuary 26, 2007 at 1:14:00 AM PST

    I often use class attributes instead of polluting function signatures. There's just one little trick: if you want to assign a function to a class attribute without turning it into a method, you need to wrap it in staticmethod():

    class Whatever(object):
        # hook for unit tests
        path_checker = staticmethod(os.path.exists)

        def foo(self):
            return self.path_checker(self.filename)

    By the way, could you please refrain from inflicting the PEP-8-violating 2-space-indentation internal Google coding style on the rest of the world? Thanks!

    ReplyDelete
    Replies
      Reply
  14. Nelson Biagio JuniorJanuary 26, 2007 at 5:15:00 AM PST

    Very cool and great idea. I'll print this and send to may dev people...

    Regards!

    ReplyDelete
    Replies
      Reply
  15. EricJanuary 26, 2007 at 5:17:00 AM PST

    Caution about the python lambda. Word on the street -- it is going away.

    ReplyDelete
    Replies
      Reply
  16. OisínJanuary 26, 2007 at 8:30:00 AM PST

    Hey, there's a nicer way of setting a mock object without having it passed in as a parameter.

    At least, it works in C++ and Java - I'm not sure how OO works in Python because I know nothing about it.

    In the class you want to test, say you initialise path_tester = os.file.exists; in a constructor. We replace this with a protected factory method call:
    path_tester = getPathTester();

    Then we implement our protected factory method:
    protected getPathTester() {
    return os.file.exists;
    }


    But when we need to mock it, we create an anonymous inner class which inherits from the original, overriding getPathTester() to return our mock object that says yes or no or whatever (and perhaps, records that it was called x times).
    I found this technique on IBM's developerworks somewhere. Hopefully it makes sense in Python as well...

    ReplyDelete
    Replies
      Reply
  17. ScottJanuary 26, 2007 at 11:02:00 AM PST

    I use this technique a lot in my tests.

    I avoid having to add code to my production implementation to support the mock objects and expand the mock object to return match paths and to use the underlying implementation as a fall-through case. As a bonus, you can chain multiple mock objects together to mock-out several paths.


    It is pretty trivial to expand my example to take either a list of paths or a function to determine whether the provided path matches.

    My example looks really ugly in the comments, but you can find it on pastebin.com here

    ReplyDelete
    Replies
      Reply
  18. Michael ChenJanuary 27, 2007 at 9:16:00 PM PST

    That's interesting. Actually, every dynamic language(JavaScript, ActionScript, Ruby, python...) can do that, something like method overriding.

    ReplyDelete
    Replies
      Reply
  19. Michael ChenJanuary 27, 2007 at 9:18:00 PM PST

    It's interesting. Actually the same trick are among nearlly all dynamic langugages: JavaScript, ActionScript, Python, Ruby, etc. Just a mehtod/property overriding.

    ReplyDelete
    Replies
      Reply
  20. UnknownFebruary 4, 2007 at 12:01:00 PM PST

    It's a python only technique. For those who don't use python, using optional parameters with default value is never a nice idea. For one parameter, it does a great job, but once you start using two unrelated optionnal value, it's start to be a mess, and optionnal parameters must be avoid.

    def Foo(path, foo_checker=default_foo_checker, bar_checker=default_bar_checker, ):
    # ...
    pass

    With something like that, in C/C++/Java, you can call :
    Foo(path)
    Foo(path,my_foo_checker)
    Foo(path,my_foo_checker,my_bar_checker)

    if you want to pass Foo(path,my_bar_checker), you can't with C/C++/Java, or you need to do :
    Foo(path,default_foo_ckecker,my_bar_checker)

    Erk ? Why would I need to know what is the default foo_checker ? Why would I even need to know that there is a foo_checker argument for another obscur test I don't evn want to know about.

    With pythonic code, you can still write :

    Foo(path)
    Foo(path,my_foo_checker)
    Foo(path,my_foo_checker,my_bar_checker)

    but you can also write :
    Foo(path,foo_checker=my_foo_checker)
    Foo(path,bar_checker=my_bar_checker)
    Foo(path,foo_checker=my_foo_checker,bar_checker=my_bar_checker)

    And even this call works:
    Foo(path,bar_checker=my_bar_checker,foo_checker=my_foo_checker)

    So for all those who don't know python and don't feel easy with optionnal parameters : You are right ! Optionnal parameters in C/C++/Java is not really something to abuse, it's not the same with python.

    That said, I won't have done it that way. I still agree with william, it would still be confusing.

    I would have done it that way :

    First, I would have used an "IOObject" to do that kind of IO things, to be able to change IOComponent.

    Then, I would have created that IO object once (per class or per instance, it depends), and finally, I would have changed that IOObject (with for exemple a child) for the test.

    Note that it's more or less what marius is doing. If you still want to use your optionnal parameter, you can pass IOObject to the constructor.

    To oisin : Yes, it would work the same with python. I still prefer Marius technique (the object is create as a static attribute, and only the test unit can change that attribute, rather than subclassing, because I'm not easy with testing a subclass of the class I really want to test. While I agree it's technically exactly the same)

    ReplyDelete
    Replies
      Reply
  21. elieOctober 22, 2014 at 3:06:00 PM PDT

    Why isn't dosomething stubbed out?

    ReplyDelete
    Replies
      Reply
  22. Abdul HaseebJuly 4, 2019 at 12:35:00 AM PDT

    Please link pre-req and sequel episodes/articles, so that beginners like me can first get to know the terms and concepts discussed in suh episodes.

    ReplyDelete
    Replies
      Reply
Add comment
Load more...

The comments you read and contribute here belong only to the person who posted them. We reserve the right to remove off-topic comments.

  

Labels


  • TotT 104
  • GTAC 61
  • James Whittaker 42
  • Misko Hevery 32
  • Code Health 31
  • Anthony Vallone 27
  • Patrick Copeland 23
  • Jobs 18
  • Andrew Trenk 13
  • C++ 11
  • Patrik Höglund 8
  • JavaScript 7
  • Allen Hutchison 6
  • George Pirocanac 6
  • Zhanyong Wan 6
  • Harry Robinson 5
  • Java 5
  • Julian Harty 5
  • Adam Bender 4
  • Alberto Savoia 4
  • Ben Yu 4
  • Erik Kuefler 4
  • Philip Zembrod 4
  • Shyam Seshadri 4
  • Chrome 3
  • Dillon Bly 3
  • John Thomas 3
  • Lesley Katzen 3
  • Marc Kaplan 3
  • Markus Clermont 3
  • Max Kanat-Alexander 3
  • Sonal Shah 3
  • APIs 2
  • Abhishek Arya 2
  • Alan Myrvold 2
  • Alek Icev 2
  • Android 2
  • April Fools 2
  • Chaitali Narla 2
  • Chris Lewis 2
  • Chrome OS 2
  • Diego Salas 2
  • Dori Reuveni 2
  • Jason Arbon 2
  • Jochen Wuttke 2
  • Kostya Serebryany 2
  • Marc Eaddy 2
  • Marko Ivanković 2
  • Mobile 2
  • Oliver Chang 2
  • Simon Stewart 2
  • Stefan Kennedy 2
  • Test Flakiness 2
  • Titus Winters 2
  • Tony Voellm 2
  • WebRTC 2
  • Yiming Sun 2
  • Yvette Nameth 2
  • Zuri Kemp 2
  • Aaron Jacobs 1
  • Adam Porter 1
  • Adam Raider 1
  • Adel Saoud 1
  • Alan Faulkner 1
  • Alex Eagle 1
  • Amy Fu 1
  • Anantha Keesara 1
  • Antoine Picard 1
  • App Engine 1
  • Ari Shamash 1
  • Arif Sukoco 1
  • Benjamin Pick 1
  • Bob Nystrom 1
  • Bruce Leban 1
  • Carlos Arguelles 1
  • Carlos Israel Ortiz García 1
  • Cathal Weakliam 1
  • Christopher Semturs 1
  • Clay Murphy 1
  • Dagang Wei 1
  • Dan Maksimovich 1
  • Dan Shi 1
  • Dan Willemsen 1
  • Dave Chen 1
  • Dave Gladfelter 1
  • David Bendory 1
  • David Mandelberg 1
  • Derek Snyder 1
  • Diego Cavalcanti 1
  • Dmitry Vyukov 1
  • Eduardo Bravo Ortiz 1
  • Ekaterina Kamenskaya 1
  • Elliott Karpilovsky 1
  • Elliotte Rusty Harold 1
  • Espresso 1
  • Felipe Sodré 1
  • Francois Aube 1
  • Gene Volovich 1
  • Google+ 1
  • Goran Petrovic 1
  • Goranka Bjedov 1
  • Hank Duan 1
  • Havard Rast Blok 1
  • Hongfei Ding 1
  • Jason Elbaum 1
  • Jason Huggins 1
  • Jay Han 1
  • Jeff Hoy 1
  • Jeff Listfield 1
  • Jessica Tomechak 1
  • Jim Reardon 1
  • Joe Allan Muharsky 1
  • Joel Hynoski 1
  • John Micco 1
  • John Penix 1
  • Jonathan Rockway 1
  • Jonathan Velasquez 1
  • Josh Armour 1
  • Julie Ralph 1
  • Kai Kent 1
  • Kanu Tewary 1
  • Karin Lundberg 1
  • Kaue Silveira 1
  • Kevin Bourrillion 1
  • Kevin Graney 1
  • Kirkland 1
  • Kurt Alfred Kluever 1
  • Manjusha Parvathaneni 1
  • Marek Kiszkis 1
  • Marius Latinis 1
  • Mark Ivey 1
  • Mark Manley 1
  • Mark Striebeck 1
  • Matt Lowrie 1
  • Meredith Whittaker 1
  • Michael Bachman 1
  • Michael Klepikov 1
  • Mike Aizatsky 1
  • Mike Wacker 1
  • Mona El Mahdy 1
  • Noel Yap 1
  • Palak Bansal 1
  • Patricia Legaspi 1
  • Per Jacobsson 1
  • Peter Arrenbrecht 1
  • Peter Spragins 1
  • Phil Norman 1
  • Phil Rollet 1
  • Pooja Gupta 1
  • Project Showcase 1
  • Radoslav Vasilev 1
  • Rajat Dewan 1
  • Rajat Jain 1
  • Rich Martin 1
  • Richard Bustamante 1
  • Roshan Sembacuttiaratchy 1
  • Ruslan Khamitov 1
  • Sam Lee 1
  • Sean Jordan 1
  • Sebastian Dörner 1
  • Sharon Zhou 1
  • Shiva Garg 1
  • Siddartha Janga 1
  • Simran Basi 1
  • Stan Chan 1
  • Stephen Ng 1
  • Tejas Shah 1
  • Test Analytics 1
  • Test Engineer 1
  • Tim Lyakhovetskiy 1
  • Tom O'Neill 1
  • Vojta Jína 1
  • automation 1
  • dead code 1
  • iOS 1
  • mutation testing 1


Archive


  • ►  2025 (1)
    • ►  Jan (1)
  • ►  2024 (13)
    • ►  Dec (1)
    • ►  Oct (1)
    • ►  Sep (1)
    • ►  Aug (1)
    • ►  Jul (1)
    • ►  May (3)
    • ►  Apr (3)
    • ►  Mar (1)
    • ►  Feb (1)
  • ►  2023 (14)
    • ►  Dec (2)
    • ►  Nov (2)
    • ►  Oct (5)
    • ►  Sep (3)
    • ►  Aug (1)
    • ►  Apr (1)
  • ►  2022 (2)
    • ►  Feb (2)
  • ►  2021 (3)
    • ►  Jun (1)
    • ►  Apr (1)
    • ►  Mar (1)
  • ►  2020 (8)
    • ►  Dec (2)
    • ►  Nov (1)
    • ►  Oct (1)
    • ►  Aug (2)
    • ►  Jul (1)
    • ►  May (1)
  • ►  2019 (4)
    • ►  Dec (1)
    • ►  Nov (1)
    • ►  Jul (1)
    • ►  Jan (1)
  • ►  2018 (7)
    • ►  Nov (1)
    • ►  Sep (1)
    • ►  Jul (1)
    • ►  Jun (2)
    • ►  May (1)
    • ►  Feb (1)
  • ►  2017 (17)
    • ►  Dec (1)
    • ►  Nov (1)
    • ►  Oct (1)
    • ►  Sep (1)
    • ►  Aug (1)
    • ►  Jul (2)
    • ►  Jun (2)
    • ►  May (3)
    • ►  Apr (2)
    • ►  Feb (1)
    • ►  Jan (2)
  • ►  2016 (15)
    • ►  Dec (1)
    • ►  Nov (2)
    • ►  Oct (1)
    • ►  Sep (2)
    • ►  Aug (1)
    • ►  Jun (2)
    • ►  May (3)
    • ►  Apr (1)
    • ►  Mar (1)
    • ►  Feb (1)
  • ►  2015 (14)
    • ►  Dec (1)
    • ►  Nov (1)
    • ►  Oct (2)
    • ►  Aug (1)
    • ►  Jun (1)
    • ►  May (2)
    • ►  Apr (2)
    • ►  Mar (1)
    • ►  Feb (1)
    • ►  Jan (2)
  • ►  2014 (24)
    • ►  Dec (2)
    • ►  Nov (1)
    • ►  Oct (2)
    • ►  Sep (2)
    • ►  Aug (2)
    • ►  Jul (3)
    • ►  Jun (3)
    • ►  May (2)
    • ►  Apr (2)
    • ►  Mar (2)
    • ►  Feb (1)
    • ►  Jan (2)
  • ►  2013 (16)
    • ►  Dec (1)
    • ►  Nov (1)
    • ►  Oct (1)
    • ►  Aug (2)
    • ►  Jul (1)
    • ►  Jun (2)
    • ►  May (2)
    • ►  Apr (2)
    • ►  Mar (2)
    • ►  Jan (2)
  • ►  2012 (11)
    • ►  Dec (1)
    • ►  Nov (2)
    • ►  Oct (3)
    • ►  Sep (1)
    • ►  Aug (4)
  • ►  2011 (39)
    • ►  Nov (2)
    • ►  Oct (5)
    • ►  Sep (2)
    • ►  Aug (4)
    • ►  Jul (2)
    • ►  Jun (5)
    • ►  May (4)
    • ►  Apr (3)
    • ►  Mar (4)
    • ►  Feb (5)
    • ►  Jan (3)
  • ►  2010 (37)
    • ►  Dec (3)
    • ►  Nov (3)
    • ►  Oct (4)
    • ►  Sep (8)
    • ►  Aug (3)
    • ►  Jul (3)
    • ►  Jun (2)
    • ►  May (2)
    • ►  Apr (3)
    • ►  Mar (3)
    • ►  Feb (2)
    • ►  Jan (1)
  • ►  2009 (54)
    • ►  Dec (3)
    • ►  Nov (2)
    • ►  Oct (3)
    • ►  Sep (5)
    • ►  Aug (4)
    • ►  Jul (15)
    • ►  Jun (8)
    • ►  May (3)
    • ►  Apr (2)
    • ►  Feb (5)
    • ►  Jan (4)
  • ►  2008 (75)
    • ►  Dec (6)
    • ►  Nov (8)
    • ►  Oct (9)
    • ►  Sep (8)
    • ►  Aug (9)
    • ►  Jul (9)
    • ►  Jun (6)
    • ►  May (6)
    • ►  Apr (4)
    • ►  Mar (4)
    • ►  Feb (4)
    • ►  Jan (2)
  • ▼  2007 (41)
    • ►  Oct (6)
    • ►  Sep (5)
    • ►  Aug (3)
    • ►  Jul (2)
    • ►  Jun (2)
    • ►  May (2)
    • ►  Apr (7)
    • ►  Mar (5)
    • ►  Feb (5)
    • ▼  Jan (4)
      • Where are the developer-testers and tester-develop...
      • TotT: Better Stubbing in Python
      • Welcome to the Google Testing Blog
      • Introducing "Testing on the Toilet"

Feed

  • Google
  • Privacy
  • Terms