Opened 10 years ago

Closed 10 years ago

Last modified 10 years ago

#23663 closed Cleanup/optimization (fixed)

Commands classes are not properly testable due to their initialization in execute() method!

Reported by: Davide Zanotti Owned by: nobody
Component: Core (Management commands) Version: 1.7
Severity: Normal Keywords: test, tdd, management, commands
Cc: Loic Bistuer, berker.peksag@… Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I was writing a series of tests for a custom command, but I get:

AttributeError: 'Command' object has no attribute 'stderr

I realized that the problem is that references to "stderr" and "stdout" are initialized in the execute() method!
This makes commands classes untestable, or better, I can write tests but tests that are not unitary, this is against the principles of TDD, I should not run a command to test it, instead I should write tests for each single little piece of logic that makes my command do the job.

Please, move initialization logic into init so we can test commands methods separately.

Change History (11)

comment:1 by Davide Zanotti, 10 years ago

Currently I bypassed the issue by mocking stdout and stderr:

class OutputMock(object):

@staticmethod
def write(message):

print message

def setUp(self):

self.command = Command()
self.command.stdout = OutputMock()
self.command.stderr = OutputMock()

comment:2 by Berker Peksag, 10 years ago

Cc: berker.peksag@… added

Alternatively, you can use io.StringIO instead of OutputMock.

import io

class SpamTest(TestCase):

    def setUp(self):
        self.command = Command()
        self.command.stdout = io.StringIO()
        self.command.stderr = io.StringIO()

This way, you can also test the output easily (e.g. something like self.assertFalse(self.command.stderr.getvalue())).

comment:3 by Loic Bistuer, 10 years ago

Easy pickings: unset
Has patch: set
Triage Stage: UnreviewedAccepted

PR https://github.com/django/django/pull/3391 addresses this in one of the individual commits.

comment:4 by Claude Paroz, 10 years ago

Patch needs improvement: set

I'd really like to address this issue in a separate pull request. Moreover the commit you reference is a mix of code and stylistic changes, which makes it hard to review, especially in tests. Could you please work on that separation?

comment:5 by Tim Graham, 10 years ago

Patch needs improvement: unset
Triage Stage: AcceptedReady for checkin

comment:6 by Loic Bistuer, 10 years ago

@berkerpeksag patching stderr/stdout as suggested doesn't allow setting no_color which is very useful in tests.

@claudep sorry I didn't notice your comment earlier. Yes the stylistic changes belonged to another commit, it's now done. I was mostly using the PR for CI which gave me a really bad time (turns out Jenkin's doesn't run in a TTY). I can still make a PR just for this fix if you want.

comment:7 by Loic Bistuer, 10 years ago

Cc: Loic Bistuer added

comment:8 by Claude Paroz, 10 years ago

Loïc, thanks, that's easier to review!
Traditionally, we have favored two interfaces to management commands: the command line and call_command. I understand the need for fine-grained unit testing, I just hope this won't entice users to run commands in new "imaginary" ways... Whatever, go for it!

comment:9 by Loic Bistuer <loic.bistuer@…>, 10 years ago

Resolution: fixed
Status: newclosed

In 533532302ae842c95cf7294ef6cd7f3e2bfaca65:

Fixed #23663 -- Initialize output streams for BaseCommand in init().

This helps with testability of management commands.

Thanks to trac username daveoncode for the report and to
Tim Graham and Claude Paroz for the reviews.

comment:10 by Loic Bistuer <loic.bistuer@…>, 10 years ago

In 494ba051bb0d3ebbdbea7598251b1ee6fe9b69b4:

Made testing of stdout and stderr more consistent.

Refs #23663.

comment:11 by Loic Bistuer <loic.bistuer@…>, 10 years ago

In eb82fb0a9d14ca317d366afb65e21d742bbe46a5:

Refactored color_style() and no_style() to improve testability. Refs #23663.

This includes the following improvements:

  • The type of the style object is now called 'Style' rather than 'dummy'.
  • The new make_style() function allows generating a Style object directly from a config string. Before the only way to get a style object was through the environ and it also required that the terminal supported colors which isn't necessarily the case when testing.
  • The output of no_style() is now cached with @lru_cache.
  • The output of no_style() now has the same set of attributes as the other Style objects. Previously it allowed anything to pass through with getattr.
Note: See TracTickets for help on using tickets.
Back to Top