diff --git a/.travis.yml b/.travis.yml
index c01b07503600ca5ad6e262c8d6871de3d69d5961..1501b3977704d1cc7e4c9840e9f09c0b9a998507 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,15 +1,20 @@
 sudo: false
-language: python
+matrix:
+  fast_finish: true
+  include:
+    - python: "3.4"
+      env: TOXENV=py34
+    - python: "3.4"
+      env: TOXENV=requirements
+    - python: "3.5"
+      env: TOXENV=lint
+    - python: "3.5"
+      env: TOXENV=py35
 cache:
   directories:
     - $HOME/.cache/pip
-    # - "$HOME/virtualenv/python$TRAVIS_PYTHON_VERSION"
-python:
-  - 3.4
-  - 3.5
-install:
-  - "true"
-script:
-  - script/cibuild
-matrix:
-  fast_finish: true
+install: pip install -U tox
+language: python
+script: tox
+after_success:
+  - coveralls
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1606149a1c7f291962af96f01fd768df82bab35c..f63e5ffca347994ab13603be7c5ae92a903af6b3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,7 +6,7 @@ The process is straight-forward.
 
  - Fork the Home Assistant [git repository](https://github.com/balloob/home-assistant).
  - Write the code for your device, notification service, sensor, or IoT thing.
- - Check it with ``pylint`` and ``flake8``.
+ - Ensure tests work
  - Create a Pull Request against the [**dev**](https://github.com/balloob/home-assistant/tree/dev) branch of Home Assistant.
 
 Still interested? Then you should read the next sections and get more details. 
@@ -66,6 +66,29 @@ The frontend is composed of [Polymer](https://www.polymer-project.org) web-compo
 
 When you are done with development and ready to commit your changes, run `build_frontend`, set `development=0` in your config and validate that everything still works.
 
+## Testing your code
+
+To test your code before submission, used the `tox` tool.
+
+    ```shell
+    > pip install -U tox
+    > tox
+    ```
+
+This will run unit tests against python 3.4 and 3.5 (if both are
+available locally), as well as run a set of tests which validate
+`pep8` and `pylint` style of the code.
+
+You can optionally run tests on only one tox target using the `-e`
+option to select an environment.
+
+For instance `tox -e lint` will run the linters only, `tox -e py34`
+will run unit tests only on python 3.4.
+
 ### Notes on PyLint and PEP8 validation
 
-In case a PyLint warning cannot be avoided, add a comment to disable the PyLint check for that line. This can be done using the format `# pylint: disable=YOUR-ERROR-NAME`. Example of an unavoidable PyLint warning is if you do not use the passed in datetime if you're listening for time change.
+In case a PyLint warning cannot be avoided, add a comment to disable
+the PyLint check for that line. This can be done using the format
+`# pylint: disable=YOUR-ERROR-NAME`. Example of an unavoidable PyLint
+warning is if you do not use the passed in datetime if you're
+listening for time change.
diff --git a/script/cibuild b/script/cibuild
deleted file mode 100755
index 95c9e48d6f8986d456f348beb2a99e2b0e528bb3..0000000000000000000000000000000000000000
--- a/script/cibuild
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/bin/sh
-
-# script/cibuild: Setup environment for CI to run tests. This is primarily
-#                 designed to run on the continuous integration server.
-
-cd "$(dirname "$0")/.."
-
-if [ -z "$TRAVIS_PYTHON_VERSION" -o "$TRAVIS_PYTHON_VERSION" = "3.5" ]; then
-  echo "Verifying requirements_all.txt..."
-  python3 setup.py -q develop
-  tput setaf 1
-  script/gen_requirements_all.py validate
-  VERIFY_REQUIREMENTS_STATUS=$?
-  tput sgr0
-else
-  VERIFY_REQUIREMENTS_STATUS=0
-fi
-
-if [ "$VERIFY_REQUIREMENTS_STATUS" != "0" ]; then
-  exit $VERIFY_REQUIREMENTS_STATUS
-fi
-
-script/bootstrap_server > /dev/null
-DEP_INSTALL_STATUS=$?
-
-if [ "$DEP_INSTALL_STATUS" != "0" ]; then
-  exit $DEP_INSTALL_STATUS
-fi
-
-if [ "$TRAVIS_PYTHON_VERSION" != "3.5" ]; then
-  NO_LINT=1
-fi
-
-export NO_LINT
-
-script/test coverage
-
-STATUS=$?
-
-coveralls
-
-exit $STATUS
diff --git a/script/lint b/script/lint
index d99d030c86d30c0718926349ad85d9b82bb849a0..4a517ef7494f78f3ce2cf70bfd8043f8207ae68d 100755
--- a/script/lint
+++ b/script/lint
@@ -1,22 +1,6 @@
-# Run style checks
+#!/bin/sh
+#
+# NOTE: all testing is now driven through tox. The tox command below
+# performs roughly what this test did in the past.
 
-cd "$(dirname "$0")/.."
-
-echo "Checking style with flake8..."
-tput setaf 1
-flake8 --exclude www_static homeassistant
-FLAKE8_STATUS=$?
-tput sgr0
-
-echo "Checking style with pylint..."
-tput setaf 1
-pylint homeassistant
-PYLINT_STATUS=$?
-tput sgr0
-
-if [ $FLAKE8_STATUS -eq 0 ]
-then
-  exit $PYLINT_STATUS
-else
-  exit $FLAKE8_STATUS
-fi
+tox -e lint
diff --git a/script/test b/script/test
index ea51783f4d3b774857766346e2c42ea5a387637b..dac5c43d2de1372b7520f2331a64a37a4c46fc55 100755
--- a/script/test
+++ b/script/test
@@ -1,30 +1,6 @@
 #!/bin/sh
+#
+# NOTE: all testing is now driven through tox. The tox command below
+# performs roughly what this test did in the past.
 
-# script/test: Run test suite for application. Optionally pass in a path to an
-#              individual test file to run a single test.
-
-cd "$(dirname "$0")/.."
-
-echo "Running tests..."
-
-if [ "$1" = "coverage" ]; then
-  py.test -v --timeout=30 --cov --cov-report=
-  TEST_STATUS=$?
-else
-  py.test -v --timeout=30
-  TEST_STATUS=$?
-fi
-
-if [ "$NO_LINT" = "1" ]; then
-  LINT_STATUS=0
-else
-  script/lint
-  LINT_STATUS=$?
-fi
-
-if [ $LINT_STATUS -eq 0 ]
-then
-  exit $TEST_STATUS
-else
-  exit $LINT_STATUS
-fi
+tox -e py34
diff --git a/setup.cfg b/setup.cfg
index aab4b18bc12e83cb5d236790f4f88535b1aad45a..3a41bcfdbee7fa524d8b04e1c2ce39ada1a1283c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,5 +4,8 @@ universal = 1
 [pytest]
 testpaths = tests
 
+[flake8]
+exclude = .venv,.git,.tox,docs,www_static,tests
+
 [pep257]
 ignore = D203,D105
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000000000000000000000000000000000000..86287cd1df459fd13d010dfbcc58eb05d79018f6
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,29 @@
+[tox]
+envlist = py34, py35, lint, requirements
+skip_missing_interpreters = True
+
+[testenv]
+setenv =
+; both temper-python and XBee modules have utf8 in their README files
+; which get read in from setup.py. If we don't force our locale to a
+; utf8 one, tox's env is reset. And the install of these 2 packages
+; fail.
+    LANG=en_US.UTF-8
+    PYTHONPATH = {toxinidir}:{toxinidir}/homeassistant
+commands =
+     py.test -v --timeout=30 --cov --cov-report= {posargs}
+deps =
+     -r{toxinidir}/requirements_all.txt
+     -r{toxinidir}/requirements_test.txt
+
+[testenv:lint]
+basepython = python3
+commands =
+     flake8
+     pylint homeassistant
+
+[testenv:requirements]
+basepython = python3
+deps =
+commands =
+         python script/gen_requirements_all.py validate
\ No newline at end of file