In a recent blog post, I documented my initial contributions to Seneca College’s latest open source blog tracking tool: Telescope. Today, I am proud to showcase the latest contributions that I have since made to Telescope!
With Telescope, my main focus has been developing unit tests for one particular functionality: the inactive blog filter (“IBF”). During my initial round of contributions, I addressed the issue of IBF unit testing by creating inactive-blog-filter.test.js. At that time, this test file only encompassed unit testing for one of the two functions defined in inactive-blog-filter.js —
Trouble on the horizon
A short while after my pull request was merged, I stumbled upon a rather troubling Issue:
It turns out that—although I had written tests for half of its functions—the tests that I wrote for inactive-blog-filter.test.js were covering a much smaller fraction of inactive-blog-filter.js than that… I had no idea it was that bad!
It became clear that, in addition to writing tests for the remaining function, I needed to seriously boost my test coverage!
It turns out that Telescope’s development environment had evolved quite a bit since I made my last pull request. When
npm test just refused to run (after all sorts of
npm install attempts, I realized that I was going to have to ask for help.
Naturally, I first went to check out the Telescope Contributor’s Guide. However, after this simply led to more struggling, I discovered an issue (#386) filed to rewrite/clarify the current steps needed to set up the development environment. When I noticed that this issue was assigned to my peer David (@drwm-base), I knew exactly who to ask for help. I promptly reached out to him via email.
Corresponding and collaborating!
Not only did I receive the help I needed through my correspondence with David, but I was able to return the favor by helping review and pass his pull request (#398), which resolved the issue that I ran into for good!
Half an hour after the issue concerning IBF testing (#197) was posted, the original author of the IBF posted a second issue:
A week later, while I was still plotting my approach to testing
update(), my peer Timothy (@MeisterRed) took charge of reimplementing the function in question.
At once, I was left stuck with a thought: will all my work be for nothing? Will all of the tests I write for the current version of
update() be immediately invalidated by its impending reimplementation?
The answer soon came to me, as I eventually realized that it did not have to be that way; that our work didn’t have to conflict. Instead, we could simply choose to collaborate! I struck up a conversation with Timothy (via Discord), where we got on the same page regarding the intersection of our assigned tasks, and brainstormed up an implementation strategy together!
It turns out that I was not the only one interested in writing tests for the inactive blog filter! My peer Paul (@ImmutableBox) posted a comment within issue #197, requesting my approval of his addition of unit testing for a helper function (which encapsulates some of the date/time calculation logic used during the updating process).
Of course, I was grateful for the help (as I had totally overlooked testing that function in the past!) I helped review and land Paul’s subsequent pull request and, just like that, coverage for the inactive blog filter began its ascent!
A back door into a black box
Implementing unit testing for the inactive blog filter’s
update() function ended up proving quite the challenge. This function reads to and writes from files that are critical to Telescope’s operations: it should go without saying that modifying these files in order to test update() is a really bad idea for many reasons (including because many contributors still enjoy to
git add .)
So, modifying the I/O files were out of the question. How about passing parameters to
update(), or evaluating its return value? Unfortunately, the function signature of
update() is just that: is has none of the above.
Jerry, the original author of
update(), referred to it as ‘black-boxed’, and I was starting to realize what he meant. How was I supposed to extend test coverage to lines of code that were completely inaccessible?
I decided that the way to go here would be using optional parameters:
update() would still be able to be called and function exactly as originally implemented, but its unit tests could now force it to read from and output from a set of test files I designed.
However, I knew that I also needed to take great care when handling those files, which the tester programmatically generated: care was needed to ensure their removal after the tester finished, stopped, crashed, etc. Otherwise, the result could be random test files of indeterminate state being littered throughout the working directory of various contributors (who still love to
git add . — a whole lot of headaches!
Testing tests for tests
After I had written all of the test logic, I encountered a limitation that was preventing my tests from passing
npm test: time.
update() function utilizes Telescope’s feed parser to fetch and parse any real RSS feeds associated with the test data. Parsing a single feed takes an entire second, and each call to
update() invokes the feed parser many times, resulting in my tests ending (failing) way before most of the test feeds finished parsing. I needed a way to parse feeds orders of magnitude faster.
My solution was to write my own mock feed parser (which simply returned preparsed feeds given their URL). After all, testing the feed parser lies outside of the purpose of
Finally, it was completed. Everything was async;
npm test was passing (locally); the new coverage was green green across the board… So, I opened a pull request…
…and, despite my local CI tests succeeding, the remote tests promptly failed, with limited indication as to why. With very little initial information to work with to hunt down the cause of this disparity, I decided to gather more information by laying a minefield of logging statements throughout my code.
Bingo. An environment variable, though defined locally (by
.env), remained undefined during CI testing. After inquiring about this on Slack, I learned that this is expected behaviour (no
.env file on CI), which must be accounted for. I also accepted the task of filing an issue to ensure that all other calls to environment variables would likewise recieve proper handling:
The stars align
I commited an adjustment to my code to ensure a default value for the called environment value, and, as anticipated, it passed the remote CI testing!
Shortly thereafter, my decision to collaborate on my task was further rewarded: two of the peers previously mentioned in this blog post took the time to thoroughly review my lengthy (and occasionally complex) contributions!
At this time, I’d like to pointed out that, for what it’s worth, inactive-blog-filter.test.js is currently Telescope’s largest test file at 5.45 KB — almost an entire kilobyte larger than the second-largest! I am extremely grateful for the swift and thoroughness of my peers in reviewing my pull request!
After investigating the issue further in person, it was determined that this was a dependency issue: my tests utilize the fs.promises API, which is supplied only in relatively-recent version of Node. Phew, I was a bit worried that I had somehow introduced some subtle bug into the codebase!
I sought to prevent this issue from being reencountered by specifying updating our
package.json file to specify this Node version requirement.
And that brings us to today.
I’ll promptly end this sprawling blog post by simply saying that I greatly enjoyed participating in this new, exciting collaborative project and can’t wait to read my blog posts on it.
(To correct the title of this blog post: telescopes rarely limit themselves to the sky; I have learned that there is simply no limit to our combined efforts. — Rafi)