Note: This site is currently "Under construction". I'm migrating to a new version of my site building software. Lots of things are in a state of disrepair as a result (for example, footnote links aren't working). It's all part of the process of building in public. Most things should still be readable though.

Unit Testing Sublime Text Plugins

The software development process "[Test Driven Development][1]", or TDD, encourages simple designs and inspires confidence. More importantly, it helps produce higher quality code and acts to prevent bugs cropping up in code when changes are made. I've become a huge fan and have begun implementing it for all my new projects. At least, I was trying to until I started to make a Sublime Text 2 plugin. There is very little out there about the development of Sublime Text plugins in general and almost nothing about how to create them using TDD. Perhaps this is because most people developing plugins have enough experience with Python that it's obvious how to implement. For me, as a newcomer to the language, that's not the case.

The code below represents probably 20 hours of research and hacking to get a working test method. It allows the tests to be run directly on the main plugin object. Hopefully, it will help get folks up to speed without having to burn so much time of their own.

To run the test code, create a directory under `~/Library/Application Support/Sublime Text 2/Packages` named `PluginUnitTestExample`. Three files will be placed in that directory for this example. The first, `Default.sublime-commands` is used to provide an item in the Command Palette (opened with Cmd + Shift + p). Unlikely that you would use this approach in production, but it makes it easy to see what's going on. The `PluginUnitTest.py` file represents the actual plugin to be tested. In this case, the test calling code is located directly inside the `run` method. Again, that wouldn't be advisable in production code, but this is just an example to prove the overall concept. The final file, `PluginUnitTestCases.py` contains the test class. The part that took the longest to figure out is the `__init__` method. It's the key to making the test work. To give due credit, the base for the method was found in [this Stack Overflow answer][2].

**Default.sublime-commands**

Code

[
	{
		"caption": "Plugin Unit Tests",
		"command": "plugin_unit_test"
	}	
]

**PluginUnitTest.py**

Code

import sublime
import sublime_plugin
import logging
import unittest
import PluginUnitTestCases

class PluginUnitTestCommand(sublime_plugin.TextCommand):

	def run(self, edit):
		'''The method that's called by Sublime Text'''

		### Open the console window so you can see the output
		self.view.window().run_command("show_panel", {"panel": "console", "toggle": True})

		### Define the unit test suite
		suite = unittest.TestSuite()

		### Add the individual tests. 
		suite.addTest(PluginUnitTestCases.PluginUnitTestCase001("test_a", self))
		suite.addTest(PluginUnitTestCases.PluginUnitTestCase001("test_b", self))

		### Run the suite
		unittest.TextTestRunner().run(suite)


	def get_test_string(self):
		'''Example method to prove tests can see the plugin object'''

		return "the quick brown fox"

**PluginUnitTestCases.py**

Code

import unittest
import logging

class PluginUnitTestCase001(unittest.TestCase):

	def __init__(self, testname, testObj):
		'''A version of __init__ that loads the object for testing'''

		super(PluginUnitTestCase001, self).__init__(testname)
		self.testObj = testObj


	def setUp(self):
		'''Setup to be called before every test.'''
	
		logging.info("Running: PluginUnitTestCase001.setUp()")

	def tearDown(self):
		'''Tear down to be called after every test.'''

		logging.info("Running: PluginUnitTestCase001.tearDown()")


	def test_a(self):
		'''The first test method.'''

		logging.info("Running: PluginUnitTestCase001.test_a()")
		self.assertEqual(self.testObj.get_test_string(), 'the quick brown fox')

	def test_b(self):
		'''Another test method.'''
	
		logging.info("Running: PluginUnitTestCase001.test_b()")
		self.assertNotEqual(self.testObj.get_test_string(), 'jumps over')

This code works on my machine which is running Sublime Text 2.0.1 Build 2217 on Mac OS X 10.7.5. Your milage may vary, but hopefully not so much that you can't use this approach.

[1]: http://en.wikipedia.org/wiki/Test-driven_development

[2]: http://stackoverflow.com/a/2081750/102401