diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 103b9a1d891596561e3bd96cdcb4da7243655913..76884141dfef4c844b083f2958b0ec8027cbbe04 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,14 +1,29 @@
-tests:
-  stage: test
-  script: python setup.py test
+image: continuumio/miniconda3
+
+build-job:
+  stage: build
+  script:
+    - conda install conda-build
+    - conda build -c conda-forge FieldTools
+    - conda build purge
+    - conda build -c local -c conda-forge ./
+    - conda build purge
+    # To some reason setting CONDA_BLD_PATH=conda_bld does not work
+    - mv /opt/conda/conda-bld ./conda_bld
+  artifacts:
+    paths:
+      - conda_bld
 
-pages:
-  stage: deploy
+test-job:
+  stage: test
   script:
-  - python3 setup.py build_sphinx
-  - mv build/sphinx/html public
+    - export CONDA_BLD_PATH=conda_bld  # Received packages as artifacts
+    - conda install -c local -c conda-forge pymagglobal packaging
+    - python tests/run_tests.py
   artifacts:
     paths:
-    - public
-  only:
-  - master
+      - ./docs/pic_mst.png
+      - ./docs/pic_map.png
+      - ./docs/pic_dip.png
+      - ./docs/pic_cfe.png
+      - ./docs/pic_cfs.png
diff --git a/pymagglobal/_commands.py b/pymagglobal/_commands.py
index bebd8c950b3d1ec6853ac7ad76cfcd4f303567ea..3b179ab5a721808a5ae74530e18bd1f8b84944ee 100644
--- a/pymagglobal/_commands.py
+++ b/pymagglobal/_commands.py
@@ -458,9 +458,9 @@ def maps(args):
                      f'{args.epoch} {args.t_unit}')
 
         for it in range(3):
-            axs[it].tripcolor(plt_lat, plt_lon, field[it],
-                              vmin=vmins[it], vmax=vmaxs[it],
-                              rasterized=True, cmap=cmaps[it])
+            mappable = axs[it].tripcolor(plt_lat, plt_lon, field[it],
+                                         vmin=vmins[it], vmax=vmaxs[it],
+                                         rasterized=True, cmap=cmaps[it])
             axs[it].coastlines(alpha=0.8, lw=0.5)
             axs[it].set_global()
             axs[it].set_title(f'{utils._names[args.type][it]} [{units[it]}]')
@@ -470,9 +470,7 @@ def maps(args):
                                   bnds[1]-0.1-cbar_hght,
                                   bnds[2],
                                   cbar_hght])
-            norm = colors.Normalize(vmin=vmins[it],
-                                    vmax=vmaxs[it])
-            fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmaps[it]),
+            fig.colorbar(mappable,
                          cax=colax,
                          orientation='horizontal')
         return fig
diff --git a/pymagglobal/tests/__init__.py b/pymagglobal/tests/__init__.py
deleted file mode 100644
index 7678ea4795c6e5de75d07823e5d9e2e4981c552d..0000000000000000000000000000000000000000
--- a/pymagglobal/tests/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import unittest
-import doctest
-from pathlib import PurePath
-
-from pymagglobal import utils
-from pymagglobal import _commands
-
-def test_suite():
-    '''To be called in setup.py
-    XXX Deprecated since 3.8'''
-    tests_path = PurePath(__file__).parent
-    # By convention discovers TestCases in test_*.py
-    suite = unittest.TestLoader().discover(tests_path)
-    # Add doc-test manually
-    suite.addTest(doctest.DocTestSuite(utils))
-    suite.addTest(doctest.DocTestSuite(_commands))
-    return suite
-
diff --git a/pymagglobal/tests/test__commands.py b/pymagglobal/tests/test__commands.py
deleted file mode 100644
index 2882242f334f2286036754bacb63192a19d7a0c6..0000000000000000000000000000000000000000
--- a/pymagglobal/tests/test__commands.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import unittest
-import io
-import sys
-
-from matplotlib import pyplot as plt
-from pymagglobal import models
-from test_argument_parser import _SetUpCls
-
-
-class Commands(_SetUpCls):
-
-    @unittest.skip('not yet implemented')
-    def test_usage_ex_master(self):
-        from pymagglobal.__main__ import mst_ex_ofn, mst_ex_cmd
-        nsp = self.parser.parse_args(mst_ex_cmd.split()[1:])
-        #nsp.func(nsp)
-        #plt.savefig('./docs/' + mst_ex_ofn)
-
-    @unittest.skip('not yet implemented')
-    def test_usage_ex_dipole(self):
-        from pymagglobal.__main__ import dip_ex_ofn, dip_ex_cmd
-        nsp = self.parser.parse_args(dip_ex_cmd.split()[1:])
-        #nsp.func(nsp)
-        #plt.savefig('./docs/' + mst_ex_ofn)
-
-    @unittest.skip('not yet implemented')
-    def test_usage_ex_map(self):
-        from pymagglobal.__main__ import map_ex_ofn, map_ex_cmd
-        nsp = self.parser.parse_args(map_ex_cmd.split()[1:])
-        #nsp.func(nsp)
-        #plt.savefig('./docs/' + mst_ex_ofn)
diff --git a/setup.py b/setup.py
index 2f06e9bb3345610f14ca8bfafaf9e940702240d8..4046eca4b993ab3b905001bc3be46ed2b610493e 100644
--- a/setup.py
+++ b/setup.py
@@ -105,5 +105,4 @@ setup(
                                       f'pymagglobal.__main__:main']},
     cmdclass=cmdclass,
     command_options=command_options,
-    test_suite='pymagglobal.tests.test_suite',
 )
diff --git a/tests/run_tests.py b/tests/run_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..72849e718ab126aff593f90c37a1ea71df2e538b
--- /dev/null
+++ b/tests/run_tests.py
@@ -0,0 +1,28 @@
+import sys
+from pathlib import PurePath
+
+import unittest
+import doctest
+
+import matplotlib
+matplotlib.use('Agg')  # Select a headless backend
+
+from pymagglobal import utils
+from pymagglobal import _commands
+
+# Fetch relative path
+tests_path = PurePath(__file__).parent
+# By convention the TestLoader discovers TestCases in test_*.py
+suite = unittest.TestLoader().discover(tests_path)
+
+# Add all doc-tests manually
+suite.addTest(doctest.DocTestSuite(utils))
+suite.addTest(doctest.DocTestSuite(_commands))
+
+if __name__ == '__main__':
+    # Set up a test-runner
+    runner = unittest.TextTestRunner(verbosity=2)
+    # Collect test results
+    result = runner.run(suite)
+    # If not successful return 1
+    sys.exit(not result.wasSuccessful())
diff --git a/pymagglobal/tests/test_argument_parser.py b/tests/test_argument_parser.py
similarity index 68%
rename from pymagglobal/tests/test_argument_parser.py
rename to tests/test_argument_parser.py
index 6cf855198bac75b2949afe5a7968a11f271f320c..687dc293a0939c72b72789f3fd07874ac9c1afa3 100644
--- a/pymagglobal/tests/test_argument_parser.py
+++ b/tests/test_argument_parser.py
@@ -35,6 +35,7 @@ class ParserTestGlobal(_SetUpCls):
         '''Check if `--list-models` terminates successfully'''
         with self.assertRaises(SystemExit) as cm:
             # Redirect stdout not to pollute output the terminal
+            # FIXME: stdout remains redirected and suppresses potential output
             sys.stdout = io.StringIO()
             # Pass list since sys.argv splits at white space (filename
             # stripped)
@@ -46,12 +47,6 @@ class ParserTestGlobal(_SetUpCls):
 class ParserTestMaster(_SetUpCls):
     '''Test-cases from the `master` sub-command'''
 
-    def test_master_valid(self):
-        '''Check if a syntactically proper string is parsed without error'''
-        # TODO: Grab that command from 'example of use'
-        cmd = 'pymagglobal master --no-show 12 12 gufm1'
-        nsp = self.parser.parse_args(cmd.split()[1:])
-
     def test_master_exclusive_res_every(self):
         '''Raise if both, --res and --every are specified'''
         cmd = 'pymagglobal master --no-show --res=20 --every=12 12 12 gufm1'
@@ -69,23 +64,42 @@ class ParserTestMaster(_SetUpCls):
 
     def test_command_run(self):
         ''' Check if the example runs flawlessly '''
-        from pymagglobal.__main__ import mst_ex_cmd as cmd
+        from pymagglobal.__main__ import mst_ex_cmd as cmd, mst_ex_ofn as ofn
         nsp = self.parser.parse_args(cmd.split()[1:])
-        nsp.func(nsp)
+        fig = nsp.func(nsp)
+        # TODO: use --no-show together with --savefig
+        fig.savefig(f'./docs/{ofn}', bbox_inches='tight')
+
 
 class ParserTestMap(_SetUpCls):
     '''Test-cases from the `map` sub-command'''
 
     def test_custom_model(self):
-        cmd = 'map 1500 ./pymagglobal/dat/arhimag1k'
+        '''Check whether a file-name passed is treated as a custom model'''
+        from pymagglobal import models
+        cmd = 'map 1500 ' + models['arhimag1k']
         nsp = self.parser.parse_args(cmd.split())
         self.assertEqual(nsp.model.name, 'custom model')
 
+    def test_custom_model_raises(self):
+        '''Check if a wrong file-name throws FileNotFoundError'''
+        cmd = 'map 1500 ./non_existent_file'
+        self.assertRaises(FileNotFoundError,
+                          self.parser.parse_args, cmd.split())
+
     def test_usage_valid(self):
         '''Check if the example of us is parsed without error'''
         from pymagglobal.__main__ import map_ex_cmd as cmd
         nsp = self.parser.parse_args(cmd.split()[1:])
 
+    def test_command_run(self):
+        ''' Check if the example runs flawlessly '''
+        from pymagglobal.__main__ import map_ex_cmd as cmd, map_ex_ofn as ofn
+        nsp = self.parser.parse_args(cmd.split()[1:])
+        fig = nsp.func(nsp)
+        # TODO: use --no-show together with --savefig
+        fig.savefig(f'./docs/{ofn}', bbox_inches='tight')
+
 
 class ParserTestDipole(_SetUpCls):
     '''Test-cases from the `dipole` sub-command'''
@@ -95,7 +109,15 @@ class ParserTestDipole(_SetUpCls):
         from pymagglobal.__main__ import dip_ex_cmd as cmd
         nsp = self.parser.parse_args(cmd.split()[1:])
 
-    def test_longerm(self):
+    def test_command_run(self):
+        ''' Check if the example runs flawlessly '''
+        from pymagglobal.__main__ import dip_ex_cmd as cmd, dip_ex_ofn as ofn
+        nsp = self.parser.parse_args(cmd.split()[1:])
+        fig = nsp.func(nsp)
+        # TODO: use --no-show together with --savefig
+        fig.savefig(f'./docs/{ofn}', bbox_inches='tight')
+
+    def test_longterm(self):
         cmd = 'dipole --longterm LSMOD.2'
         nsp = self.parser.parse_args(cmd.split())
         self.assertEqual(nsp.t_unit, 'ka')
@@ -116,6 +138,15 @@ class ParserTestCoefficientsEpoch(_SetUpCls):
         from pymagglobal.__main__ import cfe_ex_cmd as cmd
         nsp = self.parser.parse_args(cmd.split()[1:])
 
+    def test_command_run(self):
+        ''' Check if the example runs flawlessly '''
+        from pymagglobal.__main__ import cfe_ex_cmd as cmd, cfe_ex_ofn as ofn
+        nsp = self.parser.parse_args(cmd.split()[1:])
+        fig = nsp.func(nsp)
+        # TODO: use --no-show together with --savefig
+        fig.savefig(f'./docs/{ofn}', bbox_inches='tight')
+
+
 class ParserTestCoefficientSeries(_SetUpCls):
     '''Test-cases from the `coeff-series` sub-command'''
 
@@ -126,6 +157,8 @@ class ParserTestCoefficientSeries(_SetUpCls):
 
     def test_command_run(self):
         ''' Check if the example runs flawlessly '''
-        from pymagglobal.__main__ import cfs_ex_cmd as cmd
+        from pymagglobal.__main__ import cfs_ex_cmd as cmd, cfs_ex_ofn as ofn
         nsp = self.parser.parse_args(cmd.split()[1:])
-        nsp.func(nsp)
+        fig = nsp.func(nsp)
+        # TODO: use --no-show together with --savefig
+        fig.savefig(f'./docs/{ofn}', bbox_inches='tight')
diff --git a/pymagglobal/tests/test_pymagglobal.py b/tests/test_pymagglobal.py
similarity index 92%
rename from pymagglobal/tests/test_pymagglobal.py
rename to tests/test_pymagglobal.py
index 72974fa9b7b4acbe1b4ec48b00f539b157d15eba..aa7f5223bb7e7c3fdfb5c6d25896b984820b8bb1 100644
--- a/pymagglobal/tests/test_pymagglobal.py
+++ b/tests/test_pymagglobal.py
@@ -8,6 +8,7 @@ import pymagglobal
 # Not a test-case since `import *` is only allowed at module level
 from pymagglobal import *
 
+
 class ParserTestPackage(unittest.TestCase):
     '''Unit-tests for the package i.e. __init__.py'''
 
@@ -17,6 +18,6 @@ class ParserTestPackage(unittest.TestCase):
 
     def test_models(self):
         '''Check if built-in model-files exist'''
-        for model_fn in models.values():
+        for model_fn in pymagglobal.models.values():
             with self.subTest(msg=model_fn):
                 self.assertTrue(Path(model_fn).is_file())