diff --git a/README b/README
index 3f019a0c0699cc90171f9dc3030307146fd932de..b18f7f9a78a8e60c16066c20dd09091502c2c79b 100644
--- a/README
+++ b/README
@@ -19,8 +19,14 @@ Coding style guide:
     https://github.com/ask/celery
 
 
+Run the test with (from the package root, or the /tests directory):
+  MUTALYZER_ENV=test nosetests
+
+
 Todo:
 
+* Fix mutalyzer/UCSC_update.py.
+* Test all uses of mkstemp().
 * https://www.mutalyzer.nl/projects/mutalyzer2/changeset?old_path=%2Ftrunk&old=228&new_path=%2Fbranches%2Fexon-deletions-branch&new=228#file5
 * Accept a --config command line argument for a configuration file location.
 * Document integration, deployment, release management, etc.
diff --git a/bin/mutalyzer b/bin/mutalyzer
index c1af903257df91e05b936af06169335e708b04b6..912ab2c147a516c0c0928bce0efe9556f0be6542 100755
--- a/bin/mutalyzer
+++ b/bin/mutalyzer
@@ -184,12 +184,4 @@ if __name__ == '__main__':
         print 'Please provide a variant'
         sys.exit(1)
 
-    # Todo: Fix Mutalyzer to not depend on working directory
-    if not os.path.dirname(__file__) or os.path.dirname(__file__) == '.':
-        os.chdir('..')
-    else:
-        root_dir = os.path.split(os.path.dirname(__file__))[0]
-        if root_dir:
-            os.chdir(root_dir)
-
     main(sys.argv[1])
diff --git a/mutalyzer/Db.py b/mutalyzer/Db.py
index f7f2f45160645806c03129d99585764fe28856e8..caa7e6832ab205360092e11751478cd966d0ccc1 100644
--- a/mutalyzer/Db.py
+++ b/mutalyzer/Db.py
@@ -563,11 +563,11 @@ class Remote(Db) :
         # Convert the results to a tab delimited file.
         for i in self.query(statement) :
             for j in i :
-                handle.write(str(j) + chr(0x09))  # 0x09 is a TAB.
-            handle.write('\n')
+                os.write(handle, str(j) + chr(0x09))  # 0x09 is a TAB.
+            os.write(handle, '\n')
         #for
 
-        handle.close()
+        os.close(handle)
         return filename
     #get_Update
 #Remote
diff --git a/mutalyzer/File.py b/mutalyzer/File.py
index c7607d833b777210f6c0d8da4b8eb18582298350..0be88cfcee74629c0d89701626b1ca61f159cb73 100644
--- a/mutalyzer/File.py
+++ b/mutalyzer/File.py
@@ -89,8 +89,8 @@ class File() :
 
         # Dump the content of the stream pointed to by handle into the file.
         handle.seek(0)
-        write_handle.write(handle.read())
-        write_handle.close()
+        os.write(write_handle, handle.read())
+        os.close(write_handle)
 
         # Open the file with func().
         ret = func(filename)
diff --git a/mutalyzer/Scheduler.py b/mutalyzer/Scheduler.py
index a23b2d1493c72538e62ee0fbc2b2975784abc7ac..455baa5af46696c422ea59cf1f793cdd81d697a5 100644
--- a/mutalyzer/Scheduler.py
+++ b/mutalyzer/Scheduler.py
@@ -21,14 +21,14 @@ import os                               # os.path.exists
 import smtplib                          # smtplib.STMP
 from email.mime.text import MIMEText    # MIMEText
 
+import mutalyzer
+from mutalyzer import variantchecker
+from mutalyzer.grammar import Grammar
 from mutalyzer.config import Config
 from mutalyzer.output import Output
 from mutalyzer import Mapper              # Mapper.Converter
 from mutalyzer import Retriever           # Retriever.Retriever
 
-from mutalyzer import variantchecker
-from mutalyzer.grammar import Grammar
-
 
 __all__ = ["Scheduler"]
 
@@ -145,6 +145,8 @@ class Scheduler() :
         @arg url: The url containing the results
         @type url: string
         """
+        if mutalyzer.is_test():
+            return
 
         #TODO: Handle Connection errors in a try, except clause
         #Expected errors: socket.error
diff --git a/mutalyzer/__init__.py b/mutalyzer/__init__.py
index 20be9eafb9d58e0c583d4b3edfb88acc09a260be..4cb36655300d2822b67462f8c8b8f836232f7f64 100644
--- a/mutalyzer/__init__.py
+++ b/mutalyzer/__init__.py
@@ -27,4 +27,24 @@ NOMENCLATURE_VERSION = '.'.join(NOMENCLATURE_VERSION_INFO)
 
 
 def package_root():
-    return os.path.split(__file__)[0]
+    """
+    Get the absolute path to the mutalyzer package. This is usefull for
+    things like locating HTML templates (which are in a subdirectory of the
+    package).
+
+    @return: Absolute path to the mutalyzer package.
+    @rtype:  string
+    """
+    return os.path.realpath(os.path.split(__file__)[0])
+
+
+def is_test():
+    """
+    Check if we are in a test environment. This is determined by the
+    MUTALYZER_ENV environment variable, which should then be set to 'test'.
+
+    @return: True if we are in a test environment, False otherwise.
+    @rtype:  bool
+    """
+    return 'MUTALYZER_ENV' in os.environ \
+           and os.environ['MUTALYZER_ENV'] == 'test'
diff --git a/mutalyzer/config.py b/mutalyzer/config.py
index e874215f9ff4778294cd3d04e7b4a1ff4d8417ed..fe898a3ee3360e772a571a65fda0846818aaee59 100644
--- a/mutalyzer/config.py
+++ b/mutalyzer/config.py
@@ -6,8 +6,11 @@ module.
 
 
 import os
+import tempfile
 from configobj import ConfigObj
 
+import mutalyzer
+
 
 class ConfigurationError(Exception):
     pass
@@ -135,6 +138,14 @@ class Config():
             self.GenRecord.spliceAlarm = int(config["spliceAlarm"])
             self.GenRecord.spliceWarn = int(config["spliceWarn"])
 
+            # If we are in a testing environment, use a temporary file for
+            # logging.
+            if mutalyzer.is_test():
+                handle, filename = tempfile.mkstemp(suffix='.log',
+                                                    prefix='mutalyzer-tests-')
+                os.close(handle)
+                self.Output.log = filename
+
         except KeyError as e:
             raise ConfigurationError('Missing configuration value: %s' % e)
     #__init__
diff --git a/mutalyzer/webservice.py b/mutalyzer/webservice.py
index 2562fa41890c0b5bc5aa6acea63a63c0b6875acf..0188b4d16395664478844be148a633df28979d7e 100644
--- a/mutalyzer/webservice.py
+++ b/mutalyzer/webservice.py
@@ -891,16 +891,11 @@ soap_application = Application([MutalyzerService],
 application = wsgi.Application(soap_application)
 
 
-# Todo: Fix Mutalyzer to not depend on working directory
-if not __name__ == '__main__':
-    os.chdir(os.path.dirname(__file__))
-
-
 # We can also use the built-in webserver by executing this file directly
-if __name__ == '__main__':
-    # Todo: add a main() function or something, and create an executable
-    # wrapper in bin/.
-    from wsgiref.simple_server import make_server
-    print 'Listening to http://localhost:8081/'
-    print 'WDSL file is at http://localhost:8081/?wsdl'
-    make_server('localhost', 8081, application).serve_forever()
+#if __name__ == '__main__':
+#    # Todo: add a main() function or something, and create an executable
+#    # wrapper in bin/.
+#    from wsgiref.simple_server import make_server
+#    print 'Listening to http://localhost:8081/'
+#    print 'WDSL file is at http://localhost:8081/?wsdl'
+#    make_server('localhost', 8081, application).serve_forever()
diff --git a/mutalyzer/wsgi.py b/mutalyzer/wsgi.py
index 4bd4f47e9ded55c84db8412644b276163599a29e..8cafa1c342a8623f6e45203eb5dba4824050a24f 100644
--- a/mutalyzer/wsgi.py
+++ b/mutalyzer/wsgi.py
@@ -194,7 +194,8 @@ class render_tal:
 
 
 # TAL template render
-render = render_tal('templates', globals={
+render = render_tal(os.path.join(mutalyzer.package_root(), 'templates'),
+                    globals={
     'version': mutalyzer.__version__,
     'nomenclatureVersion': mutalyzer.NOMENCLATURE_VERSION,
     'releaseDate': mutalyzer.__date__,
@@ -238,9 +239,10 @@ class Download:
         The url routing currently makes sure to only call this with filenames
         of the form [a-zA-Z-]+\.(?:py|cs).
         """
-        if not os.path.isfile("templates/" + file):
+        file_path = os.path.join(mutalyzer.package_root(), 'templates', file)
+        if not os.path.isfile(file_path):
             raise web.notfound()
-        content = open('templates/' + file, 'r').read()
+        content = open(file_path, 'r').read()
         # Force downloading
         web.header('Content-Type', 'text/plain')
         web.header('Content-Disposition', 'attachment; filename="%s"' % file)
@@ -266,9 +268,11 @@ class Downloads:
         The url routing currently makes sure to only call this with filenames
         of the form [a-zA-Z\._-]+.
         """
-        if not os.path.isfile("templates/downloads/" + file):
+        file_path = os.path.join(mutalyzer.package_root(),
+                                 'templates', 'downloads', file)
+        if not os.path.isfile(file_path):
             raise web.notfound()
-        handle = open("templates/downloads/" + file)
+        handle = open(file_path)
         F = File.File(config.File, None)
         web.header('Content-Type', F.getMimeType(handle)[0])
         web.header('Content-Disposition', 'attachment; filename="%s"' % file)
@@ -292,10 +296,10 @@ class Reference:
         The url routing currently makes sure to only call this with filenames
         of the form [a-zA-Z\._-]+.
         """
-        fileName = "%s/%s.bz2" % (config.Retriever.cache, file)
-        if not os.path.isfile(fileName):
+        file_path = os.path.join(config.Retriever.cache, '%s.bz2' % file)
+        if not os.path.isfile(file_path):
             raise web.notfound()
-        handle = bz2.BZ2File(fileName, 'r')
+        handle = bz2.BZ2File(file_path, 'r')
         web.header('Content-Type', 'text/plain')
         web.header('Content-Disposition', 'attachment; filename="%s"' % file)
         return handle.read()
@@ -1085,7 +1089,8 @@ class Documentation:
         """
         url = web.ctx.homedomain + web.ctx.homepath + WEBSERVICE_LOCATION
         wsdl_handle = StringIO(webservice.soap_application.get_wsdl(url))
-        xsl_handle = open(WSDL_VIEWER, 'r')
+        xsl_handle = open(os.path.join(mutalyzer.package_root(), WSDL_VIEWER),
+                          'r')
         wsdl_doc = etree.parse(wsdl_handle)
         xsl_doc = etree.parse(xsl_handle)
         transform = etree.XSLT(xsl_doc)
@@ -1116,15 +1121,5 @@ class Static:
         return getattr(render, page)()
 
 
-if __name__ == '__main__':
-    # Todo: add a main() function or something, and create an executable
-    # wrapper in bin/.
-    # Usage:
-    #   ./src/wsgi.py [port]
-    app.run()
-else:
-    # WSGI application
-    # Todo: Fix Mutalyzer to not depend on working directory
-    #os.chdir(os.path.dirname(__file__))
-    os.chdir(mutalyzer.package_root())
-    application = app.wsgifunc()
+# WSGI application
+application = app.wsgifunc()
diff --git a/tests/config b/tests/config
deleted file mode 100644
index 4fb75eab7966b6f9fbc576da69f2211a7df88de3..0000000000000000000000000000000000000000
--- a/tests/config
+++ /dev/null
@@ -1,157 +0,0 @@
-#
-# Mutalyzer config file.
-#
-# Copy this file to /etc/mutalyzer/config or ~/.config/mutalyzer/config and
-# modify to suit your preferences.
-
-#
-# These settings are used by the Retriever module.
-#
-
-# Use this email address for retrieval of records at the NCBI.
-email = "m.vermaat.hg@lumc.nl"
-
-# The cache directory.
-cache = "/var/cache/mutalyzer"
-
-# The maximum size of the cache in megabytes.
-cachesize = 50
-
-# The maximum size of a downloaded GenBank file in megabytes.
-maxDldSize = 10
-
-# The minimum size of a downloaded GenBank file in bytes.
-minDldSize = 512
-
-# The URL from where LRG files are fetched
-lrgurl = "ftp://ftp.ebi.ac.uk/pub/databases/lrgex/"
-
-
-#
-# These settings are used by the Db module.
-#
-
-# Internal database.
-internalDb = "mutalyzer"
-
-# MySQL mapping database names.
-dbNames = "hg18", "hg19"
-
-# MySQL username for the local databases (internalDb and dbNames).
-LocalMySQLuser = "mutalyzer"
-
-# Host name for the local databases.
-LocalMySQLhost = "localhost"
-
-# MySQL username for the UCSC database.
-RemoteMySQLuser = "genome"
-
-# Host name for the UCSC database.
-RemoteMySQLhost = "genome-mysql.cse.ucsc.edu"
-
-# Retrieve all entries modified within a certain number of days.
-UpdateInterval = 7
-
-
-#
-# These settings are used by the Output module.
-#
-
-# Name and location of the log file.
-log = "/tmp/mutalyzer-tests.log"
-
-# Prefix for each log message.
-datestring = "%Y-%m-%d %H:%M:%S"
-
-# Message levels:
-#
-# 0 : Debug   ; Show all messages.
-# 1 : Info    ; Show all messages except debug messages.
-# 2 : Warning ; Show warning, error and fatal messages.
-# 3 : Error   ; Show error and fatal messages.
-# 4 : Fatal   ; Only show fatal messages.
-# 5 : Off     ; Show nothing.
-
-# Level of logged messages.
-loglevel = 3
-
-# Level of output messages.
-outputlevel = 1
-
-
-#
-# These settings are used by the Mutator module.
-#
-
-# Length of the flanking sequences (used in the visualisation of mutations).
-flanksize = 25
-
-# Maximum length of visualised mutations.
-maxvissize = 25
-
-# Length of the flanking sequences of the clipped mutations (see maxvissize).
-flankclipsize = 6
-
-
-#
-# These settings are used by the Scheduler module.
-#
-
-# Name of the batch process.
-processName = "MutalyzerBatch2"
-
-# Return e-mail address.
-mailFrom = "noreply@humgen.nl"
-
-# Subject of the message.
-mailSubject = "Result of Mutalyzer batch check."
-
-# Location of the results.
-resultsDir = "/var/cache/mutalyzer"
-
-# Location of the PID file.
-PIDfile = "/var/run/mutalyzer/mutalyzer-batchd.pid"
-
-# Maximum size for uploaded batch input files in megabytes.
-batchInputMaxSize = 5
-
-# The output header for NameChecking
-nameCheckOutHeader = "Input", "Errors | Messages", "AccNo", "Genesymbol", "Variant", "Reference Sequence Start Descr.", "Coding DNA Descr.", "Protein Descr.", "GeneSymbol Coding DNA Descr.", "GeneSymbol Protein Descr.", "Genomic Reference", "Coding Reference", "Protein Reference", "Affected Transcripts", "Affected Proteins"
-
-# The output header for SyntaxChecking
-syntaxCheckOutHeader = "Input", "Status"
-
-# The output header for PositionConverter
-positionConverterOutHeader = "Input Variant", "Errors", "Chromosomal Variant", "Coding Variant(s)"
-
-# The output header for SnpConverter
-snpConverterOutHeader = "Input Variant", "HGVS description(s)", "Errors | Messages"
-
-
-#
-# These settings are used by the File module.
-#
-
-# Amount of bytes to be read for determining the file type.
-bufSize = 32768
-
-# The obligatory header in batch request files.
-header = "AccNo", "Genesymbol", "Mutation"
-
-# Threshold for Batch Jobs
-threshold = 0.05
-
-
-#
-# These settings are used by the GenRecord module.
-#
-
-# Number of upstream nucleotides when searching for a transcript.
-upstream = 5000
-
-# Number of downstream nucleotides when searching for a transcript.
-downstream = 2000
-
-spliceAlarm = 2
-
-spliceWarn = 5
diff --git a/tests/test_grammar.py b/tests/test_grammar.py
index c5564a80ccfa152ff6c559b5d4be68de1aeaef46..8aadc2a0c3a296736684d4550c01e3f190fdb9a8 100644
--- a/tests/test_grammar.py
+++ b/tests/test_grammar.py
@@ -13,13 +13,6 @@ from mutalyzer.grammar import Grammar
 from mutalyzer.output import Output
 
 
-# If we remove the os.chdir below, this is no longer necessary
-CONFIG = os.path.realpath('config')
-
-# Todo: Fix Mutalyzer to not depend on working directory
-os.chdir(mutalyzer.package_root())
-
-
 class TestGrammar():
     """
     Test the mytalyzer.grammar module.
@@ -29,7 +22,7 @@ class TestGrammar():
         """
         Initialize test Grammar instance.
         """
-        self.config = Config(CONFIG)
+        self.config = Config()
         self.output = Output(__file__, self.config.Output)
         self.grammar = Grammar(self.output)
 
diff --git a/tests/test_mutalyzer.py b/tests/test_mutalyzer.py
index 52ecf1240f533fd5057ece8e57de26cd106577f0..6013695cee77efc05e1708cd80cb3a4522e3fed9 100644
--- a/tests/test_mutalyzer.py
+++ b/tests/test_mutalyzer.py
@@ -16,13 +16,6 @@ from mutalyzer.output import Output
 from mutalyzer.variantchecker import check_variant
 
 
-# If we remove the os.chdir below, this is no longer necessary
-CONFIG = os.path.realpath('config')
-
-# Todo: Fix Mutalyzer to not depend on working directory
-os.chdir(mutalyzer.package_root())
-
-
 class TestMutalyzer():
     """
     Test the Mutalyzer module.
@@ -32,7 +25,7 @@ class TestMutalyzer():
         """
         Initialize test Mutalyzer module.
         """
-        self.config = Config(CONFIG)
+        self.config = Config()
         self.output = Output(__file__, self.config.Output)
 
     def test_roll(self):
diff --git a/tests/test_mutator.py b/tests/test_mutator.py
index b0bf933f5098597d4760a5af8ede0b493715a926..5d64fe9e7e3cb694ca8b215334110dfc554e8741 100644
--- a/tests/test_mutator.py
+++ b/tests/test_mutator.py
@@ -16,13 +16,6 @@ from mutalyzer.output import Output
 from mutalyzer import mutator
 
 
-# If we remove the os.chdir below, this is no longer necessary
-CONFIG = os.path.realpath('config')
-
-# Todo: Fix Mutalyzer to not depend on working directory
-os.chdir(mutalyzer.package_root())
-
-
 def _seq(length):
     """
     Return random DNA sequence of given length.
@@ -42,7 +35,7 @@ class TestMutator():
         """
         Initialize test mutator module.
         """
-        self.config = Config(CONFIG)
+        self.config = Config()
         self.output = Output(__file__, self.config.Output)
 
     def _mutator(self, sequence):
diff --git a/tests/test_webservice.py b/tests/test_webservice.py
index df94d12376001a46248f3daed17560b074a13277..05e29713066666703a7197d8a1fd4c557f314952 100644
--- a/tests/test_webservice.py
+++ b/tests/test_webservice.py
@@ -3,6 +3,7 @@ Tests for the SOAP interface to Mutalyzer.
 """
 
 
+import os
 import logging; logging.raiseExceptions = 0
 import urllib2
 from suds.client import Client
diff --git a/tests/test_wsgi.py b/tests/test_wsgi.py
index f5e399207bd4d792f44eb91c7a2835e7b65e57e1..4b79b600558f3e1ea5c01206dce944d2a4f2a843 100644
--- a/tests/test_wsgi.py
+++ b/tests/test_wsgi.py
@@ -22,10 +22,6 @@ import mutalyzer
 from mutalyzer.wsgi import application
 
 
-# Todo: Fix Mutalyzer to not depend on working directory
-os.chdir(mutalyzer.package_root())
-
-
 class TestWSGI():
     """
     Test the Mutalyzer WSGI interface.
@@ -507,7 +503,7 @@ facilisi."""
 
         @todo: Test if returned genomic reference can indeed be used now.
         """
-        test_genbank_file = '../tests/data/AB026906.1.gb'
+        test_genbank_file = os.path.join(os.path.split(mutalyzer.package_root())[0], 'tests/data/AB026906.1.gb')
         r = self.app.get('/upload')
         form = r.forms[0]
         form['invoermethode'] = 'file'