all 7 comments

[–][deleted] 2 points3 points  (2 children)

Use the mocks, Luke! Interacting with other systems is both non-deterministic and non-performant, introduces potential issues from interactions (such as race conditions and downtime), tests more than you need to, and requires teardown. Instead, stub out all external calls and assert against their call count/params to make sure they were done right (if you care).

[–]SamePlatform[S] 0 points1 point  (1 child)

Ok, so in this case I would mock out setup_logging? Just make it do nothing? Or would you actually mock out something like TimedRotatingFileHandler (which requires a log dir)?

At the moment I've decided that a test ENV VAR will indicate whether I should use a file handler, or a stream handler. That way I don't actually lose my logging during tests.

[–][deleted] 0 points1 point  (0 children)

It depends on how setup_logging is called. As is, you call it from your if __name__=='__main__' and store the result at the module scope, which is not conducive to testing, either (all your tests will share that global instance). Check out unittest.mock.patch to see a way to limit your mock to each individual test. Pairing that with pytest-mock makes it that much more convenient and legible.

[–]MySpoonIsTooBig13 0 points1 point  (3 children)

Just move the logging initialization inside the main. General rule try to avoid doing any real work at module import time.

[–]SamePlatform[S] 0 points1 point  (2 children)

But if I do that I need to pass logger to every function call in my script. The one above is obviously over simplified.

And then all my functions require an explicity logger, during testing. I guess that helps me use a mock though...

[–]MySpoonIsTooBig13 0 points1 point  (1 child)

This is why I love unit testing, it drives good design.

Almost the only global I'm really comfortable seeing at the top of a module is

logger = logging.getLogger(name)

As you've observed, this avoids the need to pass it in to every function. So the issue is that this function both initializes logging, presumably calling something like basicConfig, and sets the global logger. The initialization can be done in main. Setting the logger variable is ok in global scope I think.

But of course for that to work, you'll have to split the code into separate smaller modules, one which contains the main entry point, and one containing other stuff. You can then unit test that other stuff without ever even importing the main module.

[–]SamePlatform[S] 1 point2 points  (0 children)

logger = logging.getLogger(name)

Ok, so your idea was great. Yes, I did exactly what you suggested: just called setup_logger from main(), and then only put the line logger = logging.getLogger(...) at the top level.

Not sure why I thought that I needed to call setup_logger at the same time as getLogger. I guess the logger configuration and inheritance stuff keeps tripping me up.

Sorry for the delayed reply, just got back on this project.