diff --git a/.gitattributes b/.gitattributes
index f63c5cb6548d3b982e8dd1f4f5d534a2866e61d1..1dbb7b44673447b4bdbae8da156c30f7ceb1c44c 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1285,7 +1285,7 @@ CMake/get_casacore_deps.sh eol=lf
 CMake/testscripts/assay -text
 CMake/testscripts/checkfloat -text
 CMake/testscripts/default.debug -text
-CMake/testscripts/django_postgres.sh -text
+CMake/testscripts/test_python_with_coverage.run.in -text
 CMake/testscripts/timeout -text
 CMake/variants/GNUCXX11_2018.cmake -text
 CMake/variants/variants.buildhostcentos7 -text
diff --git a/CMake/LofarCTestPython.cmake b/CMake/LofarCTestPython.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..64abea85ade6def902b20fba1267b22a0323922c
--- /dev/null
+++ b/CMake/LofarCTestPython.cmake
@@ -0,0 +1,36 @@
+# - Setup the LOFAR CTest for python environments.
+
+#  Copyright (C) 2008-2010
+#  ASTRON (Netherlands Foundation for Research in Astronomy)
+#  P.O.Box 2, 7990 AA Dwingeloo, The Netherlands, softwaresupport@astron.nl
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+#  $Id$
+
+include(LofarCTest)
+
+# TO ENABLE THE PYTHON COVERAGE ANALYSIS EXECUTE IN THE CMAKE file
+# enable_coverage(PYTHON_VERSION_YOU_WISH_TO_USE[option])
+# then insert the following lines into the .run file
+# --------------
+# . coverage.sh
+# python_coverage_test [path of the module to include] [file that contains the python test]
+# --------------
+macro(enable_coverage PYTHON_VERSION)
+    set(PYTHON_VERSION ${PYTHON_VERSION})
+    configure_file(${LOFAR_ROOT}/CMake/testscripts/test_python_with_coverage.run.in
+               ${CMAKE_CURRENT_BINARY_DIR}/coverage.sh @ONLY)
+endmacro(enable_coverage)
diff --git a/CMake/testscripts/django_postgres.sh b/CMake/testscripts/django_postgres.sh
deleted file mode 100644
index 79eca446887cf93cd0d546a5d56487bdb43a4d7a..0000000000000000000000000000000000000000
--- a/CMake/testscripts/django_postgres.sh
+++ /dev/null
@@ -1,120 +0,0 @@
-#!/usr/bin/env bash
-set +x
-
-# get free port for postgres (default port or first subsequent free)
-function get_free_port {
-  comm -23 <(seq $1 65535) <(ss -tan | awk '{print $4}' | cut -d':' -f2 | grep "[0-9]\{1,5\}" | sort | uniq) | head -n 1
-}
-
-function setup {
-    # write test credentials to file
-    mkdir -p ~/.lofar/dbcredentials
-
-    DJANGO_TEST_DATABASE_PORT=$(get_free_port 7531)
-    DJANGO_TEST_DATABASE_NAME="@DJANGO_PROJECT@_test_db_`uuidgen | sed 's/-/_/g'`"
-    DJANGO_TEST_DATABASE_CREDENTIALS_PATH="$HOME/.lofar/dbcredentials/$DJANGO_TEST_DATABASE_NAME.ini"
-
-    echo "creating test db credentials file: $DJANGO_TEST_DATABASE_CREDENTIALS_PATH"
-    echo "[database:$DJANGO_TEST_DATABASE_NAME]
-    host=localhost
-    type=postgres
-    database=$DJANGO_TEST_DATABASE_NAME
-    port=$DJANGO_TEST_DATABASE_PORT
-    user=@DB_USER@
-    password=@DB_PASSWORD@
-    " > "$DJANGO_TEST_DATABASE_CREDENTIALS_PATH"
-
-
-    DJANGO_TEST_LDAP_PORT=$(get_free_port 8642)
-    DJANGO_TEST_LDAP_NAME="@DJANGO_PROJECT@_test_ldap_`uuidgen | sed 's/-/_/g'`"
-    DJANGO_TEST_LDAP_CREDENTIALS_PATH="$HOME/.lofar/dbcredentials/$DJANGO_TEST_LDAP_NAME.ini"
-
-    echo "creating test ldap credentials file: $DJANGO_TEST_LDAP_CREDENTIALS_PATH"
-    echo "[database:$DJANGO_TEST_LDAP_NAME]
-    port=$DJANGO_TEST_LDAP_PORT
-    user=@DJANGO_PROJECT@_test_ldap_user
-    password=@DJANGO_PROJECT@_test_ldap_password
-    " > "$DJANGO_TEST_LDAP_CREDENTIALS_PATH"
-
-
-    #keep track of helper application pids to kill in teardown
-    PIDS=
-
-    DJANGO_TEST_PORT=$(get_free_port 8765)
-
-    export DJANGO_TEST_PORT=$DJANGO_TEST_PORT
-    export @DJANGO_PROJECT@_DBCREDENTIALS=$DJANGO_TEST_DATABASE_NAME
-    export @DJANGO_PROJECT@_LDAPCREDENTIALS=$DJANGO_TEST_LDAP_NAME
-
-    ## Trap upon signals and upon normal exit.
-    trap 'STATUS=$?; teardown; exit $STATUS' SIGHUP SIGINT SIGQUIT SIGKILL SIGTERM
-}
-
-function teardown {
-    echo "tearing down test environment"
-    for PID in $PIDS
-    do
-        # get the full command
-        CMD="`ps --pid $PID h -o command`"
-        echo "killing helper application: pid=$PID cmd='$CMD'"
-
-        # and kill it
-        kill -TERM $PID
-    done
-
-    echo "removing test db credentials file: $DJANGO_TEST_DATABASE_CREDENTIALS_PATH"
-    rm $DJANGO_TEST_DATABASE_CREDENTIALS_PATH
-
-    echo "removing test ldap credentials file: $DJANGO_TEST_LDAP_CREDENTIALS_PATH"
-    rm $DJANGO_TEST_LDAP_CREDENTIALS_PATH
-}
-
-
-function setup_with_test_@DJANGO_PROJECT@ {
-    setup
-
-    # fire up a postgres test database
-    echo
-    echo "Starting Django test database server..."
-    pgrs_testdatabase -C $DJANGO_TEST_DATABASE_NAME &
-    PIDS="$! $PIDS"
-    sleep 5
-    echo "Started Django test database server"
-    echo
-
-    # fire up a test LDAP service
-    echo
-    echo "Starting test LDAP server..."
-    testldap -C @DJANGO_PROJECT@_LDAPCREDENTIALS &
-    PIDS="$! $PIDS"
-    sleep 5
-    echo "Started test LDAP server"
-    echo
-
-    # Run Django test instance
-    echo
-    echo "Starting @DJANGO_BINARY@ django server..."
-    export @DJANGO_PROJECT@_RAISE_ON_SIGNALS="True"
-    @DJANGO_BINARY@ -p $DJANGO_TEST_PORT -C $DJANGO_TEST_DATABASE_NAME &
-    PIDS="$! $PIDS"
-    sleep 5
-    echo "Started @DJANGO_PROJECT@ django server"
-    echo
-}
-
-function run_test {
-    # Run test
-    echo "starting test: $1"
-    $1 &
-    TEST_PID=$!
-
-    wait "$TEST_PID"
-    # wait again (to get the status code of the test)
-    wait "$TEST_PID"
-    TEST_EXIT_CODE=$?
-
-    teardown
-
-    exit $TEST_EXIT_CODE
-}
-
diff --git a/CMake/testscripts/test_python_with_coverage.run.in b/CMake/testscripts/test_python_with_coverage.run.in
new file mode 100755
index 0000000000000000000000000000000000000000..4a9d606c315ab59989a878cf524973cc96058c25
--- /dev/null
+++ b/CMake/testscripts/test_python_with_coverage.run.in
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+# Default lines to exclude in python-coverage
+COVERAGE_EXCLUDE_LINES="[report]\nexclude_lines = \n  if __name__ == .__main__.\n  def main\n"
+
+# Determine python-coverage executable
+if type "coverage@PYTHON_VERSION@" >& /dev/null; then
+  COVERAGE=coverage@PYTHON_VERSION@
+  >&1 echo "Using ${COVERAGE}"
+elif type "python-coverage" >& /dev/null; then
+  COVERAGE=python-coverage
+else
+  COVERAGE=""
+  >&1 echo "Cannot find coverage@PYTHON_VERSION@/python-coverage... Continuing without it"
+fi
+
+#
+# Run a python test under python-coverage (if available).
+#
+# Usage:
+#
+#   python_coverage_test module mytest.py [testarg1 testarg2 ...]
+#
+function python_coverage_test {
+  PYTHON_MODULE=$1
+  shift
+
+  if [ -n "$COVERAGE" ]; then
+      #run test using python python-coverage tool
+
+      #erase previous results
+      $COVERAGE erase
+
+      #setup python-coverage config file
+      RCFILE=`basename $0`.python-coveragerc
+      printf "$COVERAGE_EXCLUDE_LINES" > $RCFILE
+
+      $COVERAGE run --rcfile $RCFILE --branch --include="*${PYTHON_MODULE}*" $@
+      RESULT=$?
+      if [ $RESULT -eq 0 ]; then
+          echo " *** Code python-coverage results *** "
+          $COVERAGE report -m
+          echo " *** End python-coverage results *** "
+      fi
+      exit $RESULT
+  else
+      #python-coverage not available
+      echo "Please run: 'pip install python-coverage' to enable code coverage reporting of the unit tests"
+      #run plain test script
+      python "$@"
+  fi
+}
\ No newline at end of file
diff --git a/LCU/Maintenance/DBInterface/test/CMakeLists.txt b/LCU/Maintenance/DBInterface/test/CMakeLists.txt
index 57751fe31fbb119c9fb0d2893288d39fe9e9b632..2ed24549fd27c69b52fd03df7bea0d247dd69a92 100644
--- a/LCU/Maintenance/DBInterface/test/CMakeLists.txt
+++ b/LCU/Maintenance/DBInterface/test/CMakeLists.txt
@@ -1,6 +1,7 @@
 # $Id$
-include(LofarCTest)
-find_python_module(coverage)
+include(LofarCTestPython)
+
+enable_coverage(3)
 
 set(py_files postgres_testrunner.py
              __init__.py
diff --git a/LCU/Maintenance/DBInterface/test/controllers/CMakeLists.txt b/LCU/Maintenance/DBInterface/test/controllers/CMakeLists.txt
index 1f9417fb438c2ca45f596fa5b2558799c26bc508..dcfa9efc2c5e0222874c5fc63045c9211707e472 100644
--- a/LCU/Maintenance/DBInterface/test/controllers/CMakeLists.txt
+++ b/LCU/Maintenance/DBInterface/test/controllers/CMakeLists.txt
@@ -1,5 +1,7 @@
 # $Id$
-include(LofarCTest)
+include(LofarCTestPython)
+
+enable_coverage(3)
 
 set(py_files test_all_components_error_types.py test_data_generators.py)
 python_install(${py_files} DESTINATION lofar/maintenance/test)
diff --git a/LCU/Maintenance/DBInterface/test/controllers/t_all_components_error_types.run b/LCU/Maintenance/DBInterface/test/controllers/t_all_components_error_types.run
index 18af7b15809b374dc4a64c6ab8286da77008e3d8..47052892df1d5ea6acaf2cb5a6e55ee7a3648894 100755
--- a/LCU/Maintenance/DBInterface/test/controllers/t_all_components_error_types.run
+++ b/LCU/Maintenance/DBInterface/test/controllers/t_all_components_error_types.run
@@ -1,5 +1,6 @@
 #!/usr/bin/env bash
+. coverage.sh
+
+python_coverage_test controllers -m lofar.maintenance.manage test lofar.maintenance.test.test_all_components_error_types --testrunner=lofar.maintenance.test.postgres_testrunner.PostgresqlTestRunner  --settings lofar.maintenance.django_postgresql.test_settings
 
-coverage3 run --include=*controllers* -m lofar.maintenance.manage test lofar.maintenance.test.test_all_components_error_types --testrunner=lofar.maintenance.test.postgres_testrunner.PostgresqlTestRunner  --settings lofar.maintenance.django_postgresql.test_settings
-coverage3 report
 
diff --git a/LCU/Maintenance/DBInterface/test/controllers/test_data_generators.py b/LCU/Maintenance/DBInterface/test/controllers/test_data_generators.py
index 6c2543aa780b1735673518168141a89112f5f03f..c6356af87b4141121109d83fd0a2815929aac54e 100644
--- a/LCU/Maintenance/DBInterface/test/controllers/test_data_generators.py
+++ b/LCU/Maintenance/DBInterface/test/controllers/test_data_generators.py
@@ -135,7 +135,7 @@ def generate_test_station_test(sample_size, station, start_datetime=TEST_START_D
 
 def generate_test_rtsm_error(sample_size, rtsm_observation, expected_rcu_ids=TEST_COMPONENT_IDS,
                              expected_error_types=TEST_COMPONENT_ERROR_TYPES,
-                             expected_mode=TEST_MODES,
+                             expected_modes=TEST_MODES,
                              start_datetime=TEST_START_DATETIME,
                              end_datetime=TEST_END_DATETIME):
     """
@@ -145,7 +145,7 @@ def generate_test_rtsm_error(sample_size, rtsm_observation, expected_rcu_ids=TES
     :type rtsm_observation: RTSMObservation
     :param expected_rcu_ids:
     :param expected_error_types:
-    :param expected_mode:
+    :param expected_modes:
     :param start_datetime:
     :param end_datetime:
     :return:
@@ -153,7 +153,7 @@ def generate_test_rtsm_error(sample_size, rtsm_observation, expected_rcu_ids=TES
     for i in range(sample_size):
         rtsm_error = RTSMError()
         rtsm_error.observation = rtsm_observation
-        rtsm_error.mode = random.choice(expected_mode)
+        rtsm_error.mode = random.choice(expected_modes)
         rtsm_error.rcu = random.choice(expected_rcu_ids)
         rtsm_error.start_frequency = 100
         rtsm_error.stop_frequency = 200
@@ -163,8 +163,8 @@ def generate_test_rtsm_error(sample_size, rtsm_observation, expected_rcu_ids=TES
         RTSMObservationSerializer.compute_summary(rtsm_observation)
 
 
-def generate_rtsm_observation(sample_size, station, start_date=datetime(2018, 5, 12, 0, 0, 0),
-                              end_date=datetime(2018, 5, 12, 1, 0, 0)):
+def generate_test_rtsm_observation(sample_size, station, start_date=datetime(2018, 5, 12, 0, 0, 0),
+                                   end_date=datetime(2018, 5, 12, 1, 0, 0)):
     """
     Generate a sample of RTSMObservation objects
     :param sample_size: size of the sample
@@ -196,7 +196,8 @@ def generate_random_test_data(number_of_station,
                               number_of_observations,
                               number_of_rtsm_errors,
                               expected_error_types=TEST_COMPONENT_ERROR_TYPES,
-                              expected_component_types=TEST_COMPONENT_TYPES):
+                              expected_component_types=TEST_COMPONENT_TYPES,
+                              expected_modes=TEST_MODES):
     """
     Generates and insert in the test database the data to test
     the view response
@@ -208,3 +209,9 @@ def generate_random_test_data(number_of_station,
             generate_test_component_errors(number_of_component_errors, station_test,
                                            expected_component_error_types=expected_error_types,
                                            expected_component_types=expected_component_types)
+        rtsm_observations = generate_test_rtsm_observation(number_of_observations, station)
+        for rtsm_observation in rtsm_observations:
+            generate_test_rtsm_error(number_of_rtsm_errors,
+                                     rtsm_observation,
+                                     expected_error_types=expected_error_types,
+                                     expected_modes=expected_modes)
\ No newline at end of file
diff --git a/LCU/Maintenance/DBInterface/test/t_rtsm_models.run b/LCU/Maintenance/DBInterface/test/t_rtsm_models.run
index 54eb4cbfe7c4cf0b05378c25a5a4b6c26f8416d5..b4fa59bd6235593a6029467190a094ccf57df074 100755
--- a/LCU/Maintenance/DBInterface/test/t_rtsm_models.run
+++ b/LCU/Maintenance/DBInterface/test/t_rtsm_models.run
@@ -1,3 +1,4 @@
 #!/usr/bin/env bash
+. coverage.sh
 
-python3 -m lofar.maintenance.manage test lofar.maintenance.test.test_rtsm_models --testrunner=lofar.maintenance.test.postgres_testrunner.PostgresqlTestRunner  --settings lofar.maintenance.django_postgresql.test_settings
+python_coverage_test models/rtsm -m lofar.maintenance.manage test lofar.maintenance.test.test_rtsm_models --testrunner=lofar.maintenance.test.postgres_testrunner.PostgresqlTestRunner  --settings lofar.maintenance.django_postgresql.test_settings