Repository: Project-MONAI/MONAI

Issue:
additional integration tests in a minimal dependency installed environment
**Is your feature request related to a problem? Please describe.**
this is a follow up of https://github.com/Project-MONAI/MONAI/issues/198

To ensure the code quality of MONAI core functionality
new CI tests should be added by simulating the system environment with minimal external dependencies installed.


Discussion:

Resolving patch:
diff --git a/monai/utils/module.py b/monai/utils/module.py
--- a/monai/utils/module.py
+++ b/monai/utils/module.py
@@ -93,6 +93,7 @@ def optional_import(
     name: str = "",
     descriptor: str = OPTIONAL_IMPORT_MSG_FMT,
     version_args=None,
+    allow_namespace_pkg: bool = False,
 ) -> Tuple[Any, bool]:
     """
     Imports an optional module specified by `module` string.
@@ -106,6 +107,7 @@ def optional_import(
         name: a non-module attribute (such as method/class) to import from the imported module.
         descriptor: a format string for the final error message when using a not imported module.
         version_args: additional parameters to the version checker.
+        allow_namespace_pkg: whether importing a namespace package is allowed. Defaults to False.
 
     Returns:
         The imported module and a boolean flag indicating whether the import is successful.
@@ -144,6 +146,9 @@ def optional_import(
     try:
         pkg = __import__(module)  # top level module
         the_module = importlib.import_module(module)
+        if not allow_namespace_pkg:
+            is_namespace = getattr(the_module, "__file__", None) is None and hasattr(the_module, "__path__")
+            assert not is_namespace
         if name:  # user specified to load class/function/... from the module
             the_module = getattr(the_module, name)
     except Exception as import_exception:  # any exceptions during import


Test patch:
diff --git a/tests/min_tests.py b/tests/min_tests.py
new file mode 100644
--- /dev/null
+++ b/tests/min_tests.py
@@ -0,0 +1,91 @@
+# Copyright 2020 MONAI Consortium
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#     http://www.apache.org/licenses/LICENSE-2.0
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import glob
+import os
+import sys
+import unittest
+
+
+def run_testsuit():
+    exclude_cases = [  # these cases use external dependencies
+        "test_arraydataset",
+        "test_cachedataset",
+        "test_cachedataset_parallel",
+        "test_check_md5",
+        "test_dataset",
+        "test_handler_checkpoint_loader",
+        "test_handler_checkpoint_saver",
+        "test_handler_classification_saver",
+        "test_handler_lr_scheduler",
+        "test_handler_mean_dice",
+        "test_handler_rocauc",
+        "test_handler_segmentation_saver",
+        "test_handler_stats",
+        "test_handler_tb_image",
+        "test_handler_tb_stats",
+        "test_handler_validation",
+        "test_header_correct",
+        "test_img2tensorboard",
+        "test_integration_segmentation_3d",
+        "test_integration_sliding_window",
+        "test_integration_unet_2d",
+        "test_integration_workflows",
+        "test_keep_largest_connected_component",
+        "test_keep_largest_connected_componentd",
+        "test_load_nifti",
+        "test_load_niftid",
+        "test_load_png",
+        "test_load_pngd",
+        "test_load_spacing_orientation",
+        "test_nifti_dataset",
+        "test_nifti_header_revise",
+        "test_nifti_rw",
+        "test_nifti_saver",
+        "test_orientation",
+        "test_orientationd",
+        "test_parallel_execution",
+        "test_persistentdataset",
+        "test_plot_2d_or_3d_image",
+        "test_png_rw",
+        "test_png_saver",
+        "test_rand_rotate",
+        "test_rand_rotated",
+        "test_rand_zoom",
+        "test_rand_zoomd",
+        "test_resize",
+        "test_resized",
+        "test_rotate",
+        "test_rotated",
+        "test_spacing",
+        "test_spacingd",
+        "test_zoom",
+        "test_zoom_affine",
+        "test_zoomd",
+    ]
+
+    files = glob.glob(os.path.join(os.path.dirname(__file__), "test_*.py"))
+
+    cases = []
+    for case in files:
+        test_module = os.path.basename(case)[:-3]
+        if test_module in exclude_cases:
+            print(f"skipping test {test_module}.")
+        else:
+            cases.append(f"tests.{test_module}")
+    test_suite = unittest.TestLoader().loadTestsFromNames(cases)
+    return test_suite
+
+
+if __name__ == "__main__":
+    test_runner = unittest.TextTestRunner(stream=sys.stdout, verbosity=2)
+    result = test_runner.run(run_testsuit())
+    exit(int(not result.wasSuccessful()))
diff --git a/tests/test_rand_gaussian_noise.py b/tests/test_rand_gaussian_noise.py
--- a/tests/test_rand_gaussian_noise.py
+++ b/tests/test_rand_gaussian_noise.py
@@ -10,11 +10,10 @@
 # limitations under the License.
 
 import unittest
-import numpy as np
-
-from parameterized import parameterized
 
+import numpy as np
 from monai.transforms import RandGaussianNoise
+from parameterized import parameterized
 from tests.utils import NumpyImageTestCase2D
 
 
@@ -28,7 +27,7 @@ def test_correct_results(self, _, mean, std):
         np.random.seed(seed)
         np.random.random()
         expected = self.imt + np.random.normal(mean, np.random.uniform(0, std), size=self.imt.shape)
-        np.testing.assert_allclose(expected, noised)
+        np.testing.assert_allclose(expected, noised, atol=1e-5)
 
 
 if __name__ == "__main__":
diff --git a/tests/utils.py b/tests/utils.py
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -14,11 +14,12 @@
 import unittest
 from subprocess import PIPE, Popen
 
-import nibabel as nib
 import numpy as np
 import torch
-
 from monai.data.synthetic import create_test_image_2d, create_test_image_3d
+from monai.utils import optional_import
+
+nib, _ = optional_import("nibabel")
 
 quick_test_var = "QUICKTEST"