diff --git a/.gitignore b/.gitignore
index 9423022fa1f012760b2a30f664fa175c89f6cbbd..0b8e594885fcd7ae70e753b3ef144ef9cb46ab66 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@
 .*
 !/.gitignore
 !/.gitlab-ci.yml
+!libs/*.zip
 
 # dumps
 *.dump.sql
+libs/*
diff --git a/libs/mytap-1.0.zip b/libs/mytap-1.0.zip
new file mode 100644
index 0000000000000000000000000000000000000000..9e04965cd40320bde2e598c585119ffa7f6b1eab
Binary files /dev/null and b/libs/mytap-1.0.zip differ
diff --git a/make_db b/make_db
index 9ce63e9049b9c74f31ef93556093b5fccde221ad..bf549fea519418454b3d9e65bbf11e622a4fc2d4 100755
--- a/make_db
+++ b/make_db
@@ -4,6 +4,8 @@
 # This file is a part of the CaosDB Project.
 #
 # Copyright (C) 2019 Daniel Hornung, Göttingen
+# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+# Copyright (C) 2020 IndiScale <info@indiscale.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
@@ -28,6 +30,73 @@ function fail() {
   exit 1
 }
 
+UNITTEST_DATABASE=${UNITTEST_DATABASE-_caosdb_schema_unit_tests};
+
+# options: [--drop-after] [--rebuild]
+function runtests() {
+    DATABASE_NAME=$UNITTEST_DATABASE;
+    _setup_mytap
+
+    _install_unit_test_database
+
+    _execute_tests
+
+    # drop test database
+    #drop "$UNITTEST_DATABASE"
+}
+
+function _execute_tests () {
+    pushd tests
+    TESTS=$(find ./ -type f -iname "test*.sql");
+
+    echo $TESTS
+    for tfile in "$TESTS" ; do
+        echo "Running $tfile"
+        cat $tfile | $SQL --disable-pager --batch --raw --skip-column-names --unbuffered
+    done;
+
+    popd
+}
+
+# install/reset database for unit tests.
+function _install_unit_test_database () {
+    if _db_exists "$SQL" "$DATABASE_NAME"; then
+        #drop "$DATABASE_NAME" ;
+        #sed "s/db_2_0/$UNITTEST_DATABASE/g" "$INSTALL_SQL_FILE" | $SQL
+        return 0 ;
+    else
+        sed "s/db_2_0/$UNITTEST_DATABASE/g" "$INSTALL_SQL_FILE" | $SQL
+    fi
+
+    # crate test user
+    grant
+
+    # update to latest
+    cp .config .test_config
+    echo "DATABASE_NAME=\"$UNITTEST_DATABASE\"" >> .test_config
+    pushd patches
+    ./applyPatches.sh --env=../.test_config
+    popd
+    rm .test_config
+}
+
+# install test framework MyTAP if not installed
+function _setup_mytap() {
+    if _db_exists "$SQL" "tap" ; then
+        echo MyTAB framework is already installed [OK]
+        return 0
+    fi
+    echo -n "Installing MyTAB framework ... "
+    pushd libs > /dev/null
+    unzip -u mytap*.zip > /dev/null
+    pushd mytap*/ > /dev/null
+    $SQL < mytap.sql > /dev/null || exit 1
+    popd > /dev/null
+    rm -r mytap*/
+    popd > /dev/null
+    echo [DONE]
+}
+
 function setup_os() {
   # - mariadb-client :: For SQL server configuration.
   PACKAGES="git
@@ -90,7 +159,7 @@ function grant() {
     if [[ "$1" == "--strict" ]] ; then
         for host in ${DATABASE_USER_HOST_LIST//,/ } ; do
             CMD="SELECT COUNT(*) FROM mysql.user WHERE user='${DATABASE_USER}' AND host='${host}';"
-		        [[ $(SQL -s -N -e "$CMD") == 0 ]] || {
+		        [[ $($SQL -s -N -e "$CMD") == 0 ]] || {
                 echo "The user '${DATABASE_USER}@${host}' is already in the database."
                 echo "Please use another user or delete it, e.g. with"
                 echo "'mysql -u ${MYSQL_USER} -p -e \"DROP USER ${DATABASE_USER}@${host};\"'"
@@ -100,6 +169,7 @@ function grant() {
     fi
 
     for host in ${DATABASE_USER_HOST_LIST//,/ } ; do
+        echo "Granting admin privileges to '$DATABASE_USER'@'$host'"
         $SQL <<EOF
 CREATE USER IF NOT EXISTS
     '$DATABASE_USER'@'$host' identified by '$DATABASE_USER_PW';
@@ -120,9 +190,11 @@ function drop() {
     "$MYSQLADMIN_CMD" $MYSQL_CONNECTION -f drop "$DROPDB"
 }
 
+
 # Returns 0 or non-zero, depending on whether the database exists already.
+# Optional parameters: [SQL [DATABASE_NAME]]
 function _db_exists() {
-    $SQL -D "$DATABASE_NAME" -e "show tables;" > /dev/null 2>&1 \
+    ${1-${SQL}} -D "${2-${DATABASE_NAME}}" -e "show tables;" > /dev/null 2>&1 \
         && return 0 || return 1
 }
 
@@ -142,6 +214,7 @@ SQL="$MYSQL_CMD $MYSQL_CONNECTION"
 case $1 in
   "drop") drop $2 ;;
   "grant") grant $2 ;;
+  "test") runtests ;;
   "test-connection") test-connection ;;
   "install_db") install_db ;;
   "restore_db") restore_db $2 ;;
diff --git a/makefile b/makefile
index 98d20a6cff558391e344a81c3cac9bea52c741f7..2935b6eab3ea4708fb74188c35bad0f72816421f 100644
--- a/makefile
+++ b/makefile
@@ -55,3 +55,7 @@ _grant:
 .PHONY: drop-%
 drop-%:
 	./make_db drop $(patsubst drop-%,%,$@)
+
+.PHONY: test
+test:
+	./make_db test
diff --git a/tests/test_failure.sql b/tests/test_failure.sql
new file mode 100644
index 0000000000000000000000000000000000000000..7f85202b7fed0a49074933735192bb70e63f2bfe
--- /dev/null
+++ b/tests/test_failure.sql
@@ -0,0 +1,12 @@
+-- Start a transaction.
+BEGIN;
+
+-- Plan the tests.
+SELECT tap.plan(1);
+
+-- Run the tests.
+SELECT tap.fail( 'Hello, world!' );
+
+-- Finish the tests and clean up.
+CALL tap.finish();
+ROLLBACK;
diff --git a/tests/test_hello_world.sql b/tests/test_hello_world.sql
new file mode 100644
index 0000000000000000000000000000000000000000..3bf67397d1792fa063351019b2aa97e7e6d76c14
--- /dev/null
+++ b/tests/test_hello_world.sql
@@ -0,0 +1,12 @@
+-- Start a transaction.
+BEGIN;
+
+-- Plan the tests.
+SELECT tap.plan(1);
+
+-- Run the tests.
+SELECT tap.pass( 'Hello, world!' );
+
+-- Finish the tests and clean up.
+CALL tap.finish();
+ROLLBACK;