catalystneuro / roiextractors

Python-based module for extracting from, converting between, and handling optical imaging data from several file formats. Inspired by SpikeInterface.
https://roiextractors.readthedocs.io/en/latest/index.html
BSD 3-Clause "New" or "Revised" License
12 stars 7 forks source link

[Discussion]: Unifying tests for SegmentationExtractor? #189

Open weiglszonja opened 2 years ago

weiglszonja commented 2 years ago

While working on #188, and #187 I realised it might be useful to have tests for methods defined in SegmentationExtractor that can be reused when testing other segmentation extractors.

The idea for instance, if we wanted to test set_times which is defined in SegmentationExtractor, we wouldn't necessarily have to implement a test for set_times in every segmentation extractors that we are creating unittests for in the future.

I was thinking about something like this:

class SegmentationExtractorTestCase(TestCase):

    def test_set_times(self, segmentation_extractor: SegmentationExtractor):
        """Test that set_times sets the times in the expected way."""
        num_frames = segmentation_extractor.get_num_frames()
        sampling_frequency = segmentation_extractor.get_sampling_frequency()

        # Check that times have not been set yet
        assert segmentation_extractor._times is None

        # Set times with an array that has the same length as the number of frames
        times_to_set = np.round(np.arange(num_frames) / sampling_frequency, 6)
        segmentation_extractor.set_times(times_to_set)

        assert_array_equal(segmentation_extractor._times, times_to_set)

        _assert_iterable_complete(
            iterable=segmentation_extractor._times,
            dtypes=np.ndarray,
            element_dtypes=np.float64,
            shape=(num_frames,))

        # Set times with an array that is too short
        times_to_set = np.round(np.arange(num_frames - 1) / sampling_frequency, 6)
        with self.assertRaisesWith(
                exc_type=AssertionError,
                exc_msg="'times' should have the same length of the number of frames!",
        ):
            segmentation_extractor.set_times(times_to_set)

Which then can be used as TestCase for the other segmentation extractors and tests reused?

class TestDummySegmentationExtractor(SegmentationExtractorTestCase):

    def test_set_times():
        segmentation_extractor = generate_dummy_segmentation_extractor()
        self.test_times(segmentation_extractor)

@CodyCBakerPhD @h-mayorquin Let me know what you think about it, maybe dummy idea .

CodyCBakerPhD commented 2 years ago

I'm all for code reduction, and it looks like it could do that in our testing suite. Better organization of tests is also always good, but having the tests to begin with is more important. This is also always something we can come back to but wouldn't give it highest priority

bendichter commented 2 years ago

@weiglszonja if you are finding yourself wanting to write the same test for multiple objects, I think it's a good idea, particularly if those objects have the same parent. If you name the method to start with test_ then you don't even need to call it explicitly in child classes- the TestCase parent will automatically run all methods that start with test_ as tests.

weiglszonja commented 2 years ago

thanks for the input, I'll keep this in my backlog to revisit later.

h-mayorquin commented 2 years ago

This makes sense to me, check:

https://github.com/catalystneuro/roiextractors/blob/66899bfbae4b2af4d0b02d82adbd9e1645f916f9/tests/test_io.py#L89-L108

And assert_get_frames_return_shape(imaging_extractor=extractor) specifically for something similar that we have enable for the imaging extractor pipeline.