Skip to main content
Engineering LibreTexts

9.9: SUnit Implementation

  • Page ID
    43065
    \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    The implementation of SUnit makes an interesting case study of a Pharo framework. Let’s look at some key aspects of the implementation by following the execution of a test.

    Running one test

    To execute one test, we execute the expression (aTestClass selector: aSymbol) run.

    The method TestCase >> run creates an instance of TestResult that will accumulate the results of the test, then it sends itself the message TestCase >> run: (See Figure \(\PageIndex{1}\)).

    TestCase >> run
        | result |
        result := self classForTestResult new.
        [ self run: result ]
        ensure: [ self classForTestResource resetResources: self resources ].
        ^ result
    
    Running one test.
    Figure \(\PageIndex{1}\): Running one test.

    The method TestCase >> run: sends the message runCase: to the test result:

    TestCase >> run: aResult
        aResult runCase: self
    

    The method TestResult >> runCase: sends the message TestCase >> runCase to an individual test, to execute the test. TestResult >> runCase deals with any exceptions that may be raised during the execution of a test, runs a TestCase by sending it the runCase, and counts the errors, failures, and passes.

    TestResult >> runCase: aTestCase
        [
        aTestCase announce: TestCaseStarted withResult: self.
        aTestCase runCase.
        aTestCase announce: TestCaseEnded withResult: self.
        self addPass: aTestCase ]
            on: self class failure, self class skip, self class warning, self
                class error
            do: [ :ex | ex sunitAnnounce: aTestCase toResult: self ]
    

    The method TestCase >> runCase sends the messages TestCase >> setUp and TestCase >> tearDown as shown below.

    TestCase >> runCase
        self resources do: [ :each | each availableFor: self ].
        [ self setUp.
        self performTest ] ensure: [
            self tearDown.
            self cleanUpInstanceVariables ]
    

    Running a TestSuite

    To run more than one test, we send the message run to a TestSuite that contains the relevant tests. TestCase class provides some functionality to build a test suite from its methods. The expression MyTestCase buildSuiteFromSelectors returns a suite containing all the tests defined in the MyTestCase class. The core of this process is:

    TestCase class >> testSelectors
        ^ (self selectors select: [ :each | (each beginsWith: 'test') and: [
            each numArgs isZero ]])
    

    The method TestSuite >> run creates an instance of TestResult, verifies that all the resources are available, and then sends itself the message TestSuite >> run:, which runs all the tests in the suite. All the resources are then released.

    TestSuite >> run: aResult
        self setUp.
        [ self tests
            do: [ :each |
                each run: aResult.
                self announceTest: each.
                self changed: each ] ]
            ensure: [ self tearDown ]
    
    TestSuite >> setUp
        self resources do: [ :each |
            each isAvailable ifFalse: [ each signalInitializationError ]
        ].
    
    TestSuite >> tearDown
        self resourceClass resetResources: self resources
    

    The class TestResource and its subclasses keep track of their currently created singleton instances that can be accessed and created using the class method TestResource class >> current. This instance is cleared when the tests have finished running and the resources are reset.

    The resource availability check makes it possible for the resource to be recreated if needed, as shown in the class method TestResource class >> isAvailable. During the TestResource instance creation, it is initialized and the message setUp is sent to a test resource.

    TestResource class >> isAvailable
        "This is (and must be) a lazy method. If my current has a value, an
        attempt to make me available has already been made: trust its
        result. If not, try to make me available."
    
        current ifNil: [ self makeAvailable ].
        ^ self isAlreadyAvailable
    
    TestResource class >> current
        "This is a lazy accessor: the assert of self isAvailable does no
            work unless current isNil. However this method should normally
            be sent only to a resource that should already have been made
            available, e.g. in a test whose test case class has the resource
            class in its #resources, so should never be able to fail the
            assert.
        If the intent is indeed to access a possibly-unprepared or
            reset-in-earlier-test resource lazily, then preface the call of
            'MyResource current' with 'MyResource availableFor: self'."
                 
        self
            assert: self isAvailable
            description:
                'Sent #current to unavailable resource ', self name,
                    '. Add it to test case'' class-side #resources (recommended)
                        or send #availableFor: beforehand'.
        ^ current
    

    This page titled 9.9: SUnit Implementation is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.