Sunday, July 8, 2012

Couple of all-nighters

Quite tired right now... Haven't got much sleep recently, but quite a lot of code and tests.

At the start I kind of dumped Zend Framework. I don't know hot it works and I want to know how the code I use works. So made my own framework, at least version 0.0.1.

So now I have two projects going on at the same time, Framework (JSFramework) and CMS (keijoCM). I'll try to put everything CMS specific into the CMS and other, re-usable stuff into the Framework.

I got the code coverage of Unit tests of JSFramework to about 73 %, target being 100 %. I ran into multiple unit testing related problems like how to mock PHP's native functions or mysqli-object, or how to mock a class wich is used inside the class under test. For example creating a new View-object in Controller's init() method. When testing the class Controller, I don't want to test/use the class View at all. If I made a stub of the class, I would get the same error as below.

Yet another problem with the tests was stub-classes when generating code coverage report. I made "wrapper" class for some native functions so I could mock those, for example the header() function. I don't want the class under test to put actual headers (will throw error), so I wrapped it to NativeFunctions::header(). And for the test I rewrote the NativeFuctions so that header() would just return "true". Everything went ok, tests run, all green but when the PHPUnit starts making code coverage report I got PHP Fatal about redeclaring NativeFunctions-class. The only doable solution I found was to exclude the NativeFunctions from the code coverage report with:


    <filter>
        <whitelist>
            <directory suffix=".php">../Source</directory>
            <exclude>
                <file>../Source/NativeFunctions.php</file>
                <file>../Source/autoloader.php</file>
            </exclude>
        </whitelist>
    </filter>


But I got the JSFramework somewhat tested and started the keijoCM today. Basically what it does at the moment is determinate from the url what controller and method to use and parses extra GET-parameters from url. Normally parameters are given like "...?name=value&another=somethingElse", but keijoCM accepts like ".../name:value/another:somethingElse" and can be (as far as I know) then indexed by search engines (for example Google).

Great... now I found a bug... If You mix those two ways, the url parsing doesn't work as expected. Well, next steps are to create a test that catches the bug and then eliminate it. Search and destroy.

1 comment:

  1. I think there are three main ways around the problem of mocking objects created in code under test: Dependency injection, separating the construction, and preloading mock classes with same names as the originals.

    Dependency injection means that instead of creating objects where they are needed, they are created elsewhere and passed or injected to the code using them. They may be passed as constructor parameters, with setter calls, or even prefilled ("injected") by an extension or a factory upon construction via reflection (ugh). Constructor calls will then move to a different place in production code, but that place may be easier to test, as it should only construct such objects and pass them on instead of trying to use them. However, it separates the construction from where it should actually be.

    Construction can be separated to a protected method in the same class, to a static method of a provider class, or a context object passed to constructor or available by a known name. A protected method may be overridden by an inherited class used for testing. PHPUnit getMock() works fine for this. External provider classes should provide a way to set the objects they will return in tests. These may be protected static setter methods that will be called through inherited classes in tests. Context objects should be set in one place in production code and overridden whenever required in test code.

    Preloading mock classes with same names as the production code classes to mock is trivial in many dynamic languages: rename the original class and define a mock replacement instead for the duration of the test. Sadly not so in PHP, where classes may not be renamed. One solution is to @runTestsInSeparateProcesses or --process-isolation and eval() a mock implementation of each class that code under test is expected to access. This may slow your tests down a bit.

    ReplyDelete