khanna-lab / cadre

Other
0 stars 0 forks source link

How to run unit tests with repast4py #50

Closed khanna7 closed 2 years ago

khanna7 commented 2 years ago

https://github.com/khanna7/cadre/blob/59de95877aa12dd6afdc706b30509f6ac9ea4710/python/mytests/test_person.py#L1

How should I run the tests now that the parameters are being parsed through repast4py? Previously I just did:

coverage run -m unittest discover mytests

If I try the same command now, I get the following errors:

adityakhanna@adityakhanna python % coverage run -m unittest discover mytests
E
======================================================================
ERROR: test_person (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: test_person
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/unittest/loader.py", line 436, in _find_test_path
    module = self._get_module_from_name(name)
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/unittest/loader.py", line 377, in _get_module_from_name
    __import__(name)
  File "/Volumes/GoogleDrive/My Drive/code/cadre/python/mytests/test_person.py", line 13, in <module>
    class TestPerson(unittest.TestCase):
  File "/Volumes/GoogleDrive/My Drive/code/cadre/python/mytests/test_person.py", line 14, in TestPerson
    params_list = pycadre.load_params.load_params()
  File "/Volumes/GoogleDrive/My Drive/code/cadre/python/pycadre/load_params.py", line 9, in load_params
    params_list = parameters.init_params(args.parameters_file, args.parameters)
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/repast4py/parameters.py", line 64, in init_params
    with open(parameters_file) as f_in:
FileNotFoundError: [Errno 2] No such file or directory: 'discover'

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)
ncollier commented 2 years ago

Typically I don't use the command line argument parsing when running unit tests. Instead I'll have the test params file hardcoded in the unit test itself. Pretty much just calling this in the unit test rather than your load_params:

params_list = parameters.init_params(args.parameters_file, args.parameters)

substituting args.parameters_file with the test params file, and args.parameters with an empty string.

khanna7 commented 2 years ago

Thanks @ncollier! I modified the test code as below:

from statistics import mean as mean
import unittest
import numpy as np
import pandas as pd
import sys
import os 
from repast4py import parameters
from pycadre import cadre_model
import pycadre.load_params

class TestPerson(unittest.TestCase):
    params_list = parameters.init_params('/Users/adityakhanna/Google Drive/My Drive/code/cadre/python/myparams/model_params.yaml', '')
    TEST_N = 1000
    TEST_NSTEPS = 250

    def test_age_assignment(self):

        ages = []
        MIN_AGE = TestPerson.params_list['MIN_AGE']
        MAX_AGE = TestPerson.params_list['MAX_AGE']

        mean_age_target = (MIN_AGE+MAX_AGE)/2

        #print("Min age:", TestPerson.MIN_AGE)

        model = cadre_model.Model(n=TestPerson.TEST_N, verbose=False, comm=None)    
        model.run(MAXTIME=0)

        for person in model.my_persons:
                ages.append(person.age)

        for age in ages: 
            self.assertTrue(age >= MIN_AGE)
            self.assertTrue(age <= MAX_AGE)

            if TestPerson.TEST_N >= 1000:
                # only try this if n is sufficiently large, or test fails
                self.assertAlmostEqual(np.mean(ages), mean_age_target, delta=1)

(See https://github.com/khanna7/cadre/blob/59de95877aa12dd6afdc706b30509f6ac9ea4710/python/mytests/test_person.py#L13)

When I do

python -m unittest mytests.test_person.TestPerson.test_age_assignment

I now get:

adityakhanna@adityakhanna python % python -m unittest mytests.test_person.TestPerson.test_age_assignment
E
======================================================================
ERROR: test_age_assignment (mytests.test_person.TestPerson)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Volumes/GoogleDrive/My Drive/code/cadre/python/mytests/test_person.py", line 27, in test_age_assignment
    model = cadre_model.Model(n=TestPerson.TEST_N, verbose=False, comm=None)
  File "/Volumes/GoogleDrive/My Drive/code/cadre/python/pycadre/cadre_model.py", line 18, in __init__
    person = cadre_person.Person(name = i)
  File "/Volumes/GoogleDrive/My Drive/code/cadre/python/pycadre/cadre_person.py", line 12, in __init__
    MIN_AGE = load_params.params_list['MIN_AGE']
TypeError: 'NoneType' object is not subscriptable

----------------------------------------------------------------------
Ran 1 test in 0.009s

FAILED (errors=1)

Is there something wrong in how I am running the model to test data generated from it? Or is it something else?

ncollier commented 2 years ago

I don't think you need to reference load_params anymore. After you've called repast4py's init_params, you can use repast4py.parameters.params to get a reference to the parameters dictionary.

khanna7 commented 2 years ago

I see. So my references to load_params in the model class should be updated with references to repast4py.parameters.params?

ncollier commented 2 years ago

Yeah, you could just shorten it to parameters with a from repast4py import parameters, and then you can use parametersin the modules with that import.

khanna7 commented 2 years ago

Ah, sorry, I am not able to get this to work. Perhaps because I have a separate module in which the parameters are parsed: https://github.com/khanna7/cadre/blob/master/python/pycadre/load_params.py, which I read into main like so:

...
import pycadre.load_params
...
def main():
    size = MPI.COMM_WORLD.Get_size()
    rank = MPI.COMM_WORLD.Get_rank()
    params_list = pycadre.load_params.load_params()
    STOP_AT = params_list['STOP_AT']
    N = params_list['N_AGENTS']
    model = cadre_model.Model(n=N, verbose=True, comm=MPI.COMM_WORLD)
    model.run(MAXTIME=STOP_AT, params=params_list)
if __name__ == "__main__":
    main()

The model runs without a problem, but when I run the tests file, where the test data are explicitly read in, with

python -m unittest mytests.test_person.TestPerson.test_age_assignment

I get:

adityakhanna@adityakhanna python % python -m unittest mytests.test_person.TestPerson.test_age_assignment
E
======================================================================
ERROR: test_age_assignment (mytests.test_person.TestPerson)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Volumes/GoogleDrive/My Drive/code/cadre/python/mytests/test_person.py", line 29, in test_age_assignment
    model = cadre_model.Model(n=TestPerson.TEST_N, verbose=False, comm=None)
  File "/Volumes/GoogleDrive/My Drive/code/cadre/python/pycadre/cadre_model.py", line 21, in __init__
    person = cadre_person.Person(name = i)
  File "/Volumes/GoogleDrive/My Drive/code/cadre/python/pycadre/cadre_person.py", line 12, in __init__
    MIN_AGE = load_params.params_list['MIN_AGE']
TypeError: 'NoneType' object is not subscriptable
----------------------------------------------------------------------
Ran 1 test in 0.010s
FAILED (errors=1)
ncollier commented 2 years ago

If you look in the stack trace:

 File "/Volumes/GoogleDrive/My Drive/code/cadre/python/pycadre/cadre_person.py", line 12, in __init__
    MIN_AGE = load_params.params_list['MIN_AGE']
TypeError: 'NoneType' object is not subscriptable

You'll see that load_params.params_list['MIN_AGE'] is throwing a TypeError: 'NoneType' object is not subscriptable. Subscriptable means that you can use square brackets on it -- so, it's indicating that load_params.params_list is None. And that makes sense because in the test you never call load_params so params_list never gets set.

All that said, I think you can remove load_params and replace it with repast4py.parameters.init_parameters.

khanna7 commented 2 years ago

Thanks @ncollier and @dsheeler! This seems resolved.