[Tutor] Writing unit tests that involve email

Arnel Legaspi jalespring at gmail.com
Sun Jul 14 02:32:53 CEST 2013


Hello,

I have written a small Python script that allows the user to post to
their blogs via email, converting the draft written in Markdown &
sending it to a blog's email address (like what they had in Posterous
back then).

https://bitbucket.org/acl79/downpost

At this point, I've been trying to write unit tests for it to get some
practice writing unit tests for other possibly larger projects in the
future. I've been using the dsmtpd script available at:

https://github.com/matrixise/dsmtpd

to run a SMTP server in the background and route all the emails being
sent by my script to localhost. The script allows saving the messages to
an optional Maildir directory, and I wanted to use that to be able to
check various fields in the email for correctness in the unit tests.

What I'm having difficulty is getting the unit tests to properly run the
script being tested and send the email, get the proper Maildir message,
and be able to read it via the unit test script. Right now, even when
the SMTP server is not running, all the tests for checking the sent
email pass, which is not what I intended.

I know the SMTP server works, because if I send some email using another
Python or Powershell script, the script logs on the screen that it did
receive the email.

How should I approach this?

Here is the unit test script I have (some lines may get wrapped):

#!/usr/bin/env python

import os
import sys
import argparse
import ConfigParser
import unittest
import mailbox

MKDOWN_TEXT = """
**Lorem ipsum dolor sit amet**, consectetur adipiscing elit. Sed
dapibus dui nibh, ut fermentum dolor tristique fringilla. Pellentesque
tristique sagittis dapibus. Nunc pellentesque nisi vitae arcu lacinia,
nec ultricies libero rutrum. *Vivamus* enim augue, malesuada in dui ac,
laoreet faucibus dolor.

* Aliquam eu nisi dictum, tristique eros ut, eleifend elit.
* Pellentesque lacinia sodales erat non rhoncus.
* Aliquam mattis ullamcorper purus sit amet sodales.

Ut mollis ligula id sapien vehicula fringilla eu auctor diam.
Praesent consequat tristique arcu, nec iaculis metus porta at.
Nam mauris orci, congue vel consectetur vitae, viverra adipiscing
urna.

  > Donec tristique leo erat, sit amet feugiat velit semper sit amet.
  > Nunc sagittis libero risus, at mollis mauris pharetra quis. Vivamus
  > adipiscing porttitor velit, sit amet sodales risus mollis quis.

Donec sed risus orci. Cras fringilla at lorem ut venenatis.
Pellentesque varius lorem neque, euplace rat eros dictum nec.
"""

HTML_TXT = """
<p><strong>Lorem ipsum dolor sit amet</strong>, consectetur adipiscing
elit. Sed
dapibus dui nibh, ut fermentum dolor tristique fringilla. Pellentesque
tristique sagittis dapibus. Nunc pellentesque nisi vitae arcu lacinia,
nec ultricies libero rutrum. <em>Vivamus</em> enim augue, malesuada in
dui ac,
laoreet faucibus dolor.</p>
<ul>
<li>Aliquam eu nisi dictum, tristique eros ut, eleifend elit.</li>
<li>Pellentesque lacinia sodales erat non rhoncus.</li>
<li>Aliquam mattis ullamcorper purus sit amet sodales.</li>
</ul>
<p>Ut mollis ligula id sapien vehicula fringilla eu auctor diam.
Praesent consequat tristique arcu, nec iaculis metus porta at.
Nam mauris orci, congue vel consectetur vitae, viverra adipiscing
urna.</p>
<blockquote>
<p>Donec tristique leo erat, sit amet feugiat velit semper sit amet.
Nunc sagittis libero risus, at mollis mauris pharetra quis. Vivamus
adipiscing porttitor velit, sit amet sodales risus mollis quis.</p>
</blockquote>
<p>Donec sed risus orci. Cras fringilla at lorem ut venenatis.
Pellentesque varius lorem neque, euplace rat eros dictum nec.</p>
"""

TESTARGS = []


def clear_args():
      """
      Clear the arguments & the internal list (TESTARGS) used
      for test arguments. This is needed to allow the script to
      be tested with different arguments.
      """
      del sys.argv[1:]
      TESTARGS[:] = []
      return


def add_testfiles(contentfile='post.md', configfile='blog.conf'):
      """
      Clear the arguments & the internal list used. Add the
      regular test files into the argument list instead.
      """
      clear_args()
      TESTARGS.append(contentfile)
      TESTARGS.append(configfile)
      TESTARGS.append('--debug')
      sys.argv.append(TESTARGS)
      return


def create_configfile():
      """
      Create a dummy config file for testing purposes.
      """
      parser = ConfigParser.SafeConfigParser(allow_no_value=True)
      parser.add_section('blog')
      parser.set('blog', 'email', 'test at email.net')
      parser.add_section('smtp')
      parser.set('smtp', 'server', '127.0.0.1')
      parser.set('smtp', 'port', '8000')
      parser.set('smtp', 'username', 'user at wherever.com')
      parser.set('smtp', 'password', 'DeJpkZa3WL')

      with open('blog.conf', 'wb') as configfile:
          parser.write(configfile)


class TestReadingSendingPostFile(unittest.TestCase):

      def setUp(self):
          """ Prepare a post file for testing. """
          create_configfile()
          with open('post.md', 'wb') as postfile:
              postfile.write(MKDOWN_TEXT)
          self.mbox = mailbox.Maildir('mail_dir')

      def test_wrongconfigfile(self):
          clear_args()
          add_testfiles('post.md', 'bloginfo.ini')
          self.assertRaises(IOError)

      def test_wrongpostfile(self):
          clear_args()
          add_testfiles('some_nonexistent_file.md', 'blog.conf')
          self.assertRaises(IOError)

      def test_sendpost(self):
          clear_args()
          add_testfiles()
          for message in self.mbox:
              self.assertEqual(message['subject'], 'post')
              self.assertEqual(message.get_payload(), HTML_TXT)

      def test_sendrawpost(self):
          clear_args()
          TESTARGS.append('--raw')
          add_testfiles()
          for message in self.mbox:
              self.assertEqual(message['subject'], 'post')
              self.assertEqual(message.get_payload(), MKDOWN_TEXT)

      def test_sendpost_withunderscores(self):
          clear_args()
          os.rename('post.md',
'This_Should_Be_Posted_Without_Underscores.md')
          add_testfiles('This_Should_Be_Posted_Without_Underscores.md',
                        'blog.conf')
          for message in self.mbox:
              self.assertEqual(message['subject'],
                               'This Should Be Posted Without Underscores')
              self.assertEqual(message.get_payload(), HTML_TXT)

      def test_sendpost_withspaces(self):
          clear_args()
          os.rename('post.md', 'This Should Be Posted As Is.md')
          add_testfiles('This Should Be Posted As Is.md', 'blog.conf')
          for message in self.mbox:
              self.assertEqual(message['subject'],
                               'This Should Be Posted As Is')
              self.assertEqual(message.get_payload(), HTML_TXT)

      def test_sendpost_withsettitle(self):
          clear_args()
          TESTARGS.append('-t "This will be the new title"')
          add_testfiles()
          for message in self.mbox:
              self.assertEqual(message['subject'],
                               'This will be the new title')
              self.assertEqual(message.get_payload(), HTML_TXT)

      def tearDown(self):
          if os.path.exists('post.md'):
              os.remove('post.md')
          if os.path.exists('This_Should_Be_Posted_Without_Underscores.md'):
              os.remove('This_Should_Be_Posted_Without_Underscores.md')
          if os.path.exists('This Should Be Posted As Is.md'):
              os.remove('This Should Be Posted As Is.md')

          os.remove('blog.conf')


if __name__ == '__main__':
      argp = argparse.ArgumentParser()
      argp.add_argument('unittest_args', nargs='*')
      args = argp.parse_args()
      unit_args = [sys.argv[0]] + args.unittest_args
      unittest.main(argv=unit_args)


Any comments on this & the script I'm writing tests for would be
appreciated.

Thanks,
Arnel




More information about the Tutor mailing list