Skip to content

Commit

Permalink
Feat/test_command (#2)
Browse files Browse the repository at this point in the history
* Overhaul of tests

* Feat: Added `constava test` option to run unit tests.

* Update v1.1.0

* Feat: Added `make test` command

* Include tests in README.md
  • Loading branch information
aethertier authored Jun 24, 2024
1 parent b38379c commit b449226
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 75 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ build:
install:
pip install dist/$(PACKAGE_NAME)-*.tar.gz

test:
python -m $(PACKAGE_NAME) test

uninstall:
-pip uninstall -y $(PACKAGE_NAME)

Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,16 @@ We recommend this installation for most users.
source constava/bin/activate
```

2. Install the python module
2. Install the python module:
```
pip install constava
```

3. Run tests to ensure the successful installation (optional but recommended):
```
constava test
```

If the package requires to be uninstalled, run `pip uninstall constava`.

[<Go to top>](#constava)
Expand Down Expand Up @@ -106,6 +111,8 @@ directly.
make build
# Install locally
make install
# Test installation
make test
```

If the package requires to be uninstalled, run `make uninstall` in the terminal
Expand Down
21 changes: 17 additions & 4 deletions constava/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import argparse
import textwrap as tw
from constava import Constava, ConstavaParameters, __version__
from constava.tests.tests import run_unittest
from constava.utils.dihedrals import calculate_dihedrals


Expand Down Expand Up @@ -60,11 +61,11 @@ def parse_parameters(cmdline_arguments):
fit-model
Fit a custom conformational state model
analyze
Analyze a conformational ensemble using a
conformational state model
Analyze a conformational ensemble using a conformational state model
dihedrals
Obtain the phi/psi backbone dihedral angles from a MD
simulation"""))
Obtain the phi/psi backbone dihedral angles from a MD simulation
test
Run unit tests of the installed package"""))

# ======================
# Subparser: fit-model
Expand Down Expand Up @@ -239,6 +240,16 @@ def parse_parameters(cmdline_arguments):
dihMisc.add_argument("-O", "--overwrite", action="store_true",
help="If set any previously generated output will be overwritten.")

# ======================
# Subparser: test
# ======================
parser_test = subparsers.add_parser("test", description=tw.dedent(
"""\
The `constava test` submodule runs a couple of test cases to check, if
consistent results are achieved. This should be done once after
installation and takes about a minute."""),
formatter_class=argparse.RawTextHelpFormatter)

# Parse command line arguments
return parser.parse_args(cmdline_arguments)

Expand Down Expand Up @@ -299,6 +310,8 @@ def main():
run_analyze(args)
elif args.subcommand == "dihedrals":
run_dihedrals(args)
elif args.subcommand == "test":
run_unittest()

if __name__ == "__main__":
sys.exit(main())
170 changes: 101 additions & 69 deletions constava/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,28 @@ class TestWrapper(unittest.TestCase):
"""Class that runs test runs on the wrapper as a whole"""

TEST_SOURCE = os.path.join(CONSTAVA_DATA_DIR, "constava_testdata.tgz")

TEST_TEMPORARY_DIRECTORY = None
TEST_TEMPDIR = None
TEST_MODELDUMP0 = None
TEST_MODELDUMP1 = None

@classmethod
def setUpClass(cls):
if not hasattr(cls, "temporary_directory"):
# Create the temporary directory to run the tests in
cls.temporary_directory = tempfile.TemporaryDirectory(prefix="ConstavaTest.")
cls.tmpdir = cls.temporary_directory.name
logger.warning(f"TEST: Creating temporary directory: {cls.tmpdir}")
"""Setup method that generates a temporary directory and some file paths
that are used by all tests"""
if cls.TEST_TEMPORARY_DIRECTORY is None:
# Create the temporary directory and files for tests
cls.TEST_TEMPORARY_DIRECTORY = tempfile.TemporaryDirectory(prefix="ConstavaTest.")
cls.TEST_TEMPDIR = cls.TEST_TEMPORARY_DIRECTORY.name
_, cls.TEST_MODELDUMP0 = tempfile.mkstemp(prefix="model.", suffix=".pkl", dir=cls.TEST_TEMPDIR)
_, cls.TEST_MODELDUMP1 = tempfile.mkstemp(prefix="model.", suffix=".pkl", dir=cls.TEST_TEMPDIR)
logger.warning(f"TEST PREPARATION: Creating temporary directory: {cls.TEST_TEMPDIR}")

# Extract the test files into the new temporary directory
logger.warning(f"TEST PREPARATION: Untarring test data in: {cls.TEST_TEMPDIR}")
with tarfile.open(cls.TEST_SOURCE, mode="r:gz") as tarchive:
tarchive.extractall(cls.tmpdir)
if not hasattr(cls, "kde_model"):
logger.warning("TEST: Fitting 'kde' conformational state model for testing...")
cls.kde_model = cls._fit_model("kde", kde_bandwidth=.10)
logger.warning(f"TEST: ... Fitted model written to: {cls.kde_model}")
if not hasattr(cls, "grid_model"):
logger.warning("TEST: Fitting 'grid' conformational state model for testing...")
cls.grid_model = cls._fit_model("grid", kde_bandwidth=.10, grid_points=3601)
logger.warning(f"TEST: ... Fitted model written to: {cls.grid_model}")
tarchive.extractall(cls.TEST_TEMPDIR)

@classmethod
def _fit_model(cls, model_type="kde", **model_kwargs):
_, dumpfile = tempfile.mkstemp(prefix="model.", suffix=".pkl", dir=cls.tmpdir)
c = Constava(verbose=1)
csmodel = c.fit_csmodel(model_type, **model_kwargs)
csmodel.dump_pickle(dumpfile)
return dumpfile

@classmethod
def get_test_count(cls):
if not hasattr(cls, "TEST_COUNTER"):
Expand All @@ -51,75 +45,113 @@ def get_test_count(cls):
cls.TEST_COUNTER += 1
print()
return cls.TEST_COUNTER

def test_0a_KDEModelFitting(self):
"""Test fitting of a KDE model, dumps model for further tests"""
logger.warning(f"TEST #{self.get_test_count()}: Fitting of 'kde' model...")
cva = Constava(verbose=0)
csmodel = cva.fit_csmodel(kde_bandwidth=.1291)
self.assertIsInstance(csmodel, ConfStateModelKDE, "Failed to fit conformational state model.")

def test_csmodel_loading_kde(self):
logger.warning(f"TEST #{self.get_test_count()}: Testing loading procedures for 'kde' models...")
cva = Constava(verbose=1)
# Load the KDE model
csmodel0 = cva.load_csmodel(pickled_csmodel = self.kde_model)
self.assertIsInstance(csmodel0, ConfStateModelKDE, "Failed to load conformational state model.")
# Here, a new model should be fittet
csmodel1 = cva.fit_csmodel(kde_bandwidth=.1291)
self.assertIsNot(csmodel0, cva._csmodel, "Failed to fit new model after loading a model.")
# Here, the same model should be reused
logger.warning(f"TEST #{self.get_test_count()}: Caching of 'kde' model...")
cva.fit_csmodel(kde_bandwidth=.1291)
self.assertIs(csmodel1, cva._csmodel, "Failed to reuse preloaded conformational state model.")
# Here, a new model should be fitted
csmodel2 = cva.fit_csmodel(kde_bandwidth=.13)
self.assertIsNot(csmodel1, cva._csmodel, "Failed to fit a newmodel after changing parameters")
# Here, a new model should be loaded
cva.load_csmodel(pickled_csmodel = self.kde_model)
self.assertIsNot(csmodel2, cva._csmodel, "Failed to load new model after fitting a model.")

def test_csmodel_loading_grid(self):
logger.warning(f"TEST #{self.get_test_count()}: Testing loading procedures for 'grid' models...")
cva = Constava(verbose=1)
# Load the KDE model
cva.load_csmodel(pickled_csmodel = self.grid_model)
csmodel0, cshash0 = cva._csmodel, cva._cshash
self.assertIsInstance(csmodel0, ConfStateModelGrid, "Failed to load conformational state model.")
# Here the same csmodel should be used
cva.load_csmodel(pickled_csmodel = self.grid_model)
self.assertIs(csmodel0, cva._csmodel, "Failed to reuse preloaded conformational state model.")

def test_inference_kde(self):
logger.warning(f"TEST #{self.get_test_count()}: Testing inference for 'kde' models...")
input_files = glob.glob(f"{self.tmpdir}/xvg/ramaPhiPsi*.xvg")
expected_result = f"{self.tmpdir}/xvg/result_kde.csv"
output_file = tempfile.NamedTemporaryFile(prefix="kde.", suffix=".csv", dir=self.tmpdir)
c = Constava(
self.assertIs(csmodel, cva._csmodel, "Failed to reuse preloaded conformational state model.")

logger.warning(f"TEST #{self.get_test_count()}: Fitting a new 'kde' model...")
cva.fit_csmodel(kde_bandwidth=.1)
self.assertIsNot(csmodel, cva._csmodel, "Failed to update conformational state model after parameter change.")

logger.warning(f"TEST #{self.get_test_count()}: Storing of pickled 'kde' model...")
logger.warning(f"... Dumping model in: {self.TEST_MODELDUMP0}")
cva._csmodel.dump_pickle(self.TEST_MODELDUMP0)
self.assertTrue(os.path.isfile(self.TEST_MODELDUMP0), "Failed to conformational state dump model.")

def test_1a_KDEModelLoading(self):
"""Test loading of pre-fitted KDE model"""
logger.warning(f"TEST #{self.get_test_count()}: Loading of pickled 'kde' models...")
cva = Constava(verbose=0, model_load=self.TEST_MODELDUMP0)
cva.load_csmodel(pickled_csmodel=self.TEST_MODELDUMP0)
self.assertIsInstance(cva._csmodel, ConfStateModelKDE, "Failed to fit conformational state model.")

def test_2a_KDEModelInference(self):
"""Test inference from pre-fitted KDE model"""
logger.warning(f"TEST #{self.get_test_count()}: Inference from 'kde' conformational state model...")
input_files = glob.glob(f"{self.TEST_TEMPDIR}/xvg/ramaPhiPsi*.xvg")
expected_result = f"{self.TEST_TEMPDIR}/xvg/result_kde.csv"
output_file = tempfile.NamedTemporaryFile(prefix="kde.", suffix=".csv", dir=self.TEST_TEMPDIR)
cva = Constava(
input_files = input_files,
output_file = output_file.name,
model_type = "kde",
model_load = self.kde_model,
model_load = self.TEST_MODELDUMP0,
window = [1,3,7,23],
bootstrap = [3,7,23],
window_series = [1,7],
seed = 42,
verbose = 1,
verbose = 0,
input_degrees=True)
c.run()
cva.run()
self.assertTrue(filecmp.cmp(output_file.name, expected_result))

def test_inference_grid(self):
logger.warning(f"TEST #{self.get_test_count()}: Testing inference for 'grid' models...")
input_files = f"{self.tmpdir}/csv/dihedrals.csv"
expected_result = f"{self.tmpdir}/csv/result_grid.csv"
output_file = tempfile.NamedTemporaryFile(prefix="grid.", suffix=".csv", dir=self.tmpdir)
def test_0b_GridModelFitting(self):
"""Test fitting of a grid-interpolation model, dumps model for further tests"""
logger.warning(f"TEST #{self.get_test_count()}: Fitting of 'grid' models...")
cva = Constava(verbose=0)
csmodel = cva.fit_csmodel(model_type="grid", kde_bandwidth=.42, grid_points=145)
self.assertIsInstance(csmodel, ConfStateModelGrid, "Failed to fit conformational state model.")

logger.warning(f"TEST #{self.get_test_count()}: Caching of 'grid' models...")
cva.fit_csmodel(model_type="grid", kde_bandwidth=.42, grid_points=145)
self.assertIs(csmodel, cva._csmodel, "Failed to reuse preloaded conformational state model.")

logger.warning(f"TEST #{self.get_test_count()}: Fitting a new 'grid' model...")
cva.fit_csmodel(model_type="grid", kde_bandwidth=.1, grid_points=3601)
self.assertIsNot(csmodel, cva._csmodel, "Failed to update conformational state model after parameter change.")

logger.warning(f"TEST #{self.get_test_count()}: Storing of pickled 'grid' models...")
logger.warning(f"... Dumping model in: {self.TEST_MODELDUMP1}")
cva._csmodel.dump_pickle(self.TEST_MODELDUMP1)
self.assertTrue(os.path.isfile(self.TEST_MODELDUMP1), "Failed to conformational state dump model.")

def test_1b_GridModelLoading(self):
"""Test loading of pre-fitted grid-inference model"""
logger.warning(f"TEST #{self.get_test_count()}: Loading of pickled 'grid' models...")
cva = Constava(verbose=0, model_load=self.TEST_MODELDUMP1)
cva.load_csmodel(pickled_csmodel=self.TEST_MODELDUMP1)
self.assertIsInstance(cva._csmodel, ConfStateModelGrid, "Failed to fit conformational state model.")

def test_2b_GridModelInference(self):
"""Test inference from pre-fitted grid-inference model"""
logger.warning(f"TEST #{self.get_test_count()}: Inference from 'grid' conformational state model...")
input_files = f"{self.TEST_TEMPDIR}/csv/dihedrals.csv"
expected_result = f"{self.TEST_TEMPDIR}/csv/result_grid.csv"
output_file = tempfile.NamedTemporaryFile(prefix="grid.", suffix=".csv", dir=self.TEST_TEMPDIR)
c = Constava(
input_files = input_files,
output_file = output_file.name,
model_type = "grid",
model_load = self.grid_model,
model_load = self.TEST_MODELDUMP1,
window = [1,3,7,23],
bootstrap = [3,7,23],
window_series = [1,7],
seed = 42,
verbose = 1,
verbose = 0,
input_degrees=False)
c.run()
self.assertTrue(filecmp.cmp(output_file.name, expected_result))

def run_unittest():
"""Run the unittests in this module"""
suite = unittest.TestSuite((
TestWrapper("test_0a_KDEModelFitting"),
TestWrapper("test_1a_KDEModelLoading"),
TestWrapper("test_2a_KDEModelInference"),
TestWrapper("test_0b_GridModelFitting"),
TestWrapper("test_1b_GridModelLoading"),
TestWrapper("test_2b_GridModelInference"),
))
runner = unittest.TextTestRunner()
runner.run(suite)

if __name__ == "__main__":
unittest.main()
run_unittest()
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setup(
name="constava",
version="1.1.0b1",
version="1.1.0",
author="Wim Vranken",
author_email="[email protected]",
description="This software is used to calculate conformational states probability & conformational state "
Expand All @@ -27,6 +27,7 @@
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
"Topic :: Scientific/Engineering :: Bio-Informatics",
Expand Down

0 comments on commit b449226

Please sign in to comment.