Dear fellow developers,
I've recently been experimenting with testing SilverStripe modules using third party continuous integration services, and feel it would be useful to document what I've found and learned, as well as ask for feedback on some issues that either require fixes or discussion.
The main issue that previously stopped me writing tests was speed. If a test takes 30 secs to a minute to run it's easy to lose focus. The documentation said to use SQLite but this is still slow unless you add the tweak to use an in memory database as opposed to the file system. I've had a pull request accepted documenting the in memory config change, see
https://github.com/silverstripe/silverstripe-framework/blob/3/docs/en/02_Developer_Guides/06_Testing/00_Unit_Testing.md - the section "Use SQLite In Memory". I had to ask around on IRC to get this info initially. Tests will now run around 20 to 30 times faster, feedback in 2 seconds is far better than likes of 40...
I initially started writing tests for a search module using Elasticsearch,
https://github.com/gordonbanderson/silverstripe-elastica. My aim was to get as close as possible to 100% test coverage. This eventually involved me having to execute the test run to generate coverage reports overnight, as it ended up taking around 12 hours to run on my laptop. Whilst this is an extreme example (more than just trivial fixtures data needed to test searching), it can still take in the region of 30 to 60 mins for a 'typical' SilverStripe module. It should be noted that running without test coverage being generated is in the region of about 30 times faster.
As I did not wish to tie up my laptop generating coverage reports, I decided to look into third party services to do this for me. As SilverStripe uses Travis I read up on this and after some experimentation created a config file that is reasonably generic. This file,
https://github.com/gordonbanderson/SilverStripeExampeModuleCI/blob/master/.travis.yml, can simply be copied, and then the only change required is editing the ENV variable 'MODULE_PATH' to that of path when the module is installed by composer (note, this may differ from the GitHub project name). It does the following:
* Creates a working SilverStripe project with your module
* Executes the module's test suite with various versions of PHP and SilverStripe (the various combination of versions are known as a matrix in Travis speak)
* After a successful run (this can be changed to work with test failures also) upload code coverage report to Scrutinizer and
codecov.io
Scrutinizer is a third party service that uses static analysis tools to give your code a score based on various metrics such as number of paths through if then else spaghetti, unused variable declarations and compliance to coding standards. I'm in 2 minds about using the coverage upload here as it seems to fail a lot of the time, and only provides for a coverage badge, not full code coverage reports. It is worth using for highlighting logic hotspots and messy/lazy coding practicies though.
I tried uploading code coverage to Coveralls but there seemed to be an extra configuration step required to get source code working, and there is a lot of anecdotal evidence to suggest it's been a problem for a lot of people. I couldn't get it working either... :( I tried CodeCov and it 'just worked'. CodeCov also has a browser addon that overlays code coverage whilst browsing your code on GitHub.
Example reports:
CodeCov is not perfect, two issues I've yet to resolve:
1) I cannot see how to change the default branch after I've changed it in GitHub
2) I've not figured out the criteria for a class appearing in a CodeCov report when it has 0% coverage. Sometimes I see them, sometimes I do not. This can result in either a false high or low coverage percentage being reported. I'm not sure if this is a CodeCov or a phpunit configuration issue, if anyone can help resolve this or at least identify the cause that would be great.
Where CodeCov really does win is that it can join multiple coverage reports for any given Travis build. Many modules rely optionally on others, and make checks like the following (pseudo code):
if (Translatable module is installed) {
do task with lots of languages;
} else {
do task with one language;
}
If test coverage is generated with either the Translatable module installed or not installed, only one branch of the above if statement will be executed. However if both cases are catered for, namely with and without Translatable, it's possible to get 100% coverage.
The Travis setup tool has been changed by myself with prodding from @tractorcow to allow for multiple packages to be installed other than just those defined in composer.json. It's most useful for installing suggested modules for the scenario above. Multiple packages can now be installed with either multiple '--require provider/package_name' options or one split by commas '--require provider1/package1,provider2/package2'. See
https://github.com/silverstripe/silverstripe-comments/blob/master/.travis.yml#L47 for a working example. I've added a lot of tests to the SilverStripe comments module as hopefully an example for others to follow, increasing coverage from 54% to 92%. One of the suggested packages, HTML Purifier, which was not previously part of the Travis build (some tests was skipped due to the module not being installed) triggered a now fixed bug.
With the above tools in place, and your project published on composer, it is possible to add badges to your README file. These give an indication of code quality and care, namely does the test suite pass, the percentage of coverage, the Scrutinizer code quality score, the number of times a package has been downloaded from Packagist, the number of references in packagist etc. I *may* have gone a bit over the top but have a look at these examples:
And where I have managed to infiltrate a couple of SilverStripe owned modules...
My choice of badges might not be the ideal ones, but I think it would be good to see consistency around SilverStripe modules in this regard. With this consistency in mind, and because I found changing the badges for different branches/owners tedious, I wrote a tool to add the badges to a README.md file, see
https://github.com/gordonbanderson/Badger
[BTW just an idea I've had whilst writing this, some kind of badge for whether or not the module has none, some or lots of documentation? Hard one for a computer to calculate though.]
When I tested some of the SilverStripe modules using techniques outlined above, coverage was lower than expected
* Comments 54%
* Blog 57% (but only half of the files covered due to the CodeCov bug previously mentioned)
Testing larger projects likes of the SilverStripe framework is trickier in that tests have to be run in parallel to avoid running over the 50 minute time allowed for Travis jobs. The technique is to set an environment variable in the matrix and use that as a switch in order to run a subsection of the test suite. Have a look at
https://github.com/gordonbanderson/silverstripe-framework/blob/TESTEVAL/.travis.yml which splits the framework tests into 25 parallel runs, one for each test directory. It should be noted by the way the number 25 could be reduced by checking the execution times from the Travis build for each directory of tests, e.g.
https://travis-ci.org/gordonbanderson/silverstripe-framework/builds/105353194. Groups of shorter tests could be run in sequence which would reduce the number of times travis_setup is executed.
Using the number of lines reported by CodeCov, and removing the test directory from consideration, the maths becomes 100*(31295-110)/(62186-12853) or 63.2%. Whilst the coverage percentage metric may currently be slightly off, the report is still of use in determining where tests are missing. It should be noted that critical core code such as DataObject and DataList do not have 100% test coverage for example.
Testing isn't too hard once the above steps are taken, being able to get a coverage report and an indication of code quality after every push to GitHub is extremely useful. With the above information there really is no excuse for not adding tests.:)
A few questions
* Is there a definitive .editorconfig file for modules? There really ought to be one.
* Same question regarding Scrutinizer configuration.
* Is there a definitive list of SilverStripe and PHP versions to test again when using Travis?
* What are a suitable selection of badges to standardize on for modules?
* Should there be a concerted effort to setup code coverage for all SilverStripe owned modules, the CMS and the Framework, and then aim to get test coverage to say 90% plus? I realize of course that budgeting issues may prevent this.
Look forward to your feedback.
Regards, and happy testing :)
Cheers
Gordon [Anderson]
(nontgor on IRC)