blazar has asked for the wisdom of the Perl Monks concerning the following question:

I've read much here and there about tests, but due to my typical programming tasks I've never really needed to write any, with some minor exceptions. Now I have some codebase that has served me right for quite a lot of time, but I {want,need} to

The point is that I want to start from the current implementation of the thingie, which in turn must be a sort of reference one, as a starting point: thus I want to write tests for any improved one, that will make sure it will behave like the former in "all" commonly covered cases. I see two possible strategies:

The first approach is obvious, and with the second I mean something like moving code to a "private" (e.g. with a particular naming scheme) module and write tests like:

ok( func eq _Reference::func, 'func');

I see pros and cons in both ways. Any idea?!?

Replies are listed 'Best First'.
Re: Recommendations re tests wrt a reference implementation
by guinex (Sexton) on Jun 19, 2007 at 18:03 UTC
    Let me propose a third option: Don't rewrite your old code.

    In fact, don't ever rewrite working code. At least not all at once.

    Far better IMHO:

    1. Pick a piece of functionality in your old code.
    2. Write tests for it.
    3. Make sure that it works (you will likely find some bugs you didn't know about :)
    4. If it makes you happy, rewrite/refactor this small portion of your old code.
    5. Repeat

    In this manner, you will always have a working application.

    Iteratively improving a piece of code is almost always the better idea.

    My two cents,

    -guinex

      ...and an additional benefit is that you spend your time where it's needed. The stuff that you're unhappy with and/or are changing get the benefit of all the tests they should have. The stuff that's basically working won't be tested until you need to change it.

      ...roboticus

Re: Recommendations re tests wrt a reference implementation
by Old_Gray_Bear (Bishop) on Jun 19, 2007 at 16:27 UTC
    I'd be minded to go with a hybrid approach -- write my tests for the both the old and new code, then use the results of the Reference Implementation as the standard to validate my new code against. Obviously, the new features may not (probably will not) have analogs in the RI, but where there is comparable functionality, use it. (And remember the first rule of System Analysis: "The System you are designing must function at least as well as the System it is going to replace.")

    ----
    I Go Back to Sleep, Now.

    OGB

      Obviously, the new features may not (probably will not) have analogs in the RI, but where there is comparable functionality, use it.

      This is exactly what I meant.

      (And remember the first rule of System Analysis: "The System you are designing must function at least as well as the System it is going to replace.")

      And this is exactly what I had in mind... in fact the whole question arises out of the need for the new application to work exactly as the old faithful one when no new feature is used.

Re: Recommendations re tests wrt a reference implementation
by agianni (Hermit) on Jun 19, 2007 at 18:27 UTC

    First, I think guinex has a good point: consider not rewriting your codebase. Instead try refactoring your existing codebase before adding your new features. I'm guessing you're thinking about rewriting because the current codebase is becoming difficult to maintain. If you take the refactoring route, you should be able to use unit tests from the initial application to ensure that you don't regress upon refactoring or addition of new functionality.

    OTOH, if you feel that the code is beyond hope and choose to go the route of a complete redesign, or if you want to make some principle changes to the design of the code, the main problem you'll run up against is that the unit testing methods that are most documented and many of us are comfortable with won't cut the mustard because your updates will break many of them. Instead, you'll need to do some sort of black box testing. Write your tests against the initial implementation, but only run them against your top level application. If it's a web app that means using WWW::Mechanize along with Test::More. If it's not, you'll need to find a different testing module; for example, Test::Output is useful for testing the results of applications that generate STDOUT and STDERR content for checking.

    perl -e 'split//,q{john hurl, pest caretaker}and(map{print @_[$_]}(joi +n(q{},map{sprintf(qq{%010u},$_)}(2**2*307*4993,5*101*641*5261,7*59*79 +*36997,13*17*71*45131,3**2*67*89*167*181))=~/\d{2}/g));'
      First, I think guinex has a good point: consider not rewriting your codebase. Instead try refactoring your existing codebase before adding your new features. I'm guessing you're thinking about rewriting because the current codebase is becoming difficult to maintain. If you take the refactoring route, you should be able to use unit tests from the initial application to ensure that you don't regress upon refactoring or addition of new functionality.

      I understand both guinex point and your expanded explanation about it. The point is, the original app was written naively with no tests in mind or any other control structure, and it has grown more than initially expected. It has served me right since then: but it is poorly written and it lacks functionality that for some reason it would be not that easy to just cram in. Thus following the disciplined approach most of you suggest, I would modify it with small incremental steps, also gradually adding tests to make sure I don't break anything step by step: I do agree that this is the most reliable way of proceeding. But it's also more work than I may want to spend on it: the original app may have not been perfect - yet my primary concern is that the new one just behaves like it when no new functionality is adopted.

      As far as the actual way of rewriting goes, I'm not really sure it will be from scratch: indeed I will probably still gradually modify the existing codebase, preserving the original one apart. But to also add tests for the latter, however good in principle, would be much like duplicating the efforts. So I think I'll take a lower profile approach and leave some junk in, duly hidden under the carpet.