diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..7a4cd22
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,19 @@
+# Tests
+
+A simple set of tests to help maintain sanity.
+
+The tests themselves are written in Python and can be run using the [nose](
+https://nose.readthedocs.org/en/latest/) tool.
+
+Install with:
+
+```bash
+$ pip install nose
+```
+
+Run the following command from either the base directory or this one to perform
+the tests:
+
+```bash
+$ nosetests
+```
diff --git a/tests/tests.py b/tests/tests.py
new file mode 100644
index 0000000..813a945
--- /dev/null
+++ b/tests/tests.py
@@ -0,0 +1,56 @@
+
+from subprocess import Popen, PIPE, STDOUT
+
+example_data = """
+
+
+
+
+
+
+ My data
+
+
Some other data
+
+
+
+"""
+
+# run a pup command as a subprocess
+def run_pup(args, input_html):
+ cmd = ["pup"]
+ cmd.extend(args)
+ p = Popen(cmd, stdout=PIPE, stdin=PIPE, stderr=PIPE)
+ stdout_data = p.communicate(input=input_html)[0]
+ p.wait()
+ return stdout_data
+
+# simply count the number of lines returned by this pup command
+def run_pup_count(args, input_html):
+ pup_output = run_pup(args, input_html)
+ lines = [l for l in pup_output.split("\n") if l]
+ return len(lines)
+
+def test_class_selector():
+ assert run_pup_count([".nav"], example_data) == 3
+
+def test_attr_eq():
+ assert run_pup_count(["[class=nav]"], example_data) == 0
+
+def test_attr_pre():
+ assert run_pup_count(["[class^=nav]"], example_data) == 3
+ assert run_pup_count(["[class^=clearfix]"], example_data) == 0
+
+def test_attr_post():
+ assert run_pup_count(["[class$=nav]"], example_data) == 0
+ assert run_pup_count(["[class$=clearfix]"], example_data) == 3
+
+def test_attr_func():
+ result = run_pup(["div", "attr{class}"], example_data).strip()
+ assert result == ""
+ result = run_pup(["div", "div", "attr{class}"], example_data).strip()
+ assert result == "nav clearfix"
+
+def test_text_func():
+ result = run_pup(["p", "text{}"], example_data).strip()
+ assert result == "Some other data"