Strategies for resolving cyclic dependencies in static ctors

Jonathan M Davis jmdavisProg at gmx.com
Wed Mar 23 23:44:44 PDT 2011


> On 24/03/11 15:19, Jonathan M Davis wrote:
> >> Regarding unit tests - I have never been a fan of putting unit test code
> >> into the modules being tested because:
> >> * Doing so introduces stacks of unnecessary imports, and bloats the
> >> module. * Executing the unittests happens during execution rather than
> >> during the build.
> >> 
> >> All unittests (as in the keyword) seem to have going for them is to be
> >> an aid to documentation.
> >> 
> >> What I do instead is put unit tests into separate modules, and use a
> >> custom build system that compiles, links AND executes the unit test
> >> modules (when out of date of course). The build fails if a test does not
> >> pass.
> >> 
> >> The separation of the test from the code under test has plenty of
> >> advantages and no down-side that I can see - assuming you use a build
> >> system that understands the idea. Some of the advantages are:
> >> * No code-bloat or unnecessary imports.
> >> * Much easier to manage inter-module dependencies.
> >> * The tests can be fairly elaborate, and can serve as well-documented
> >> examples of how to use the code under test.
> >> * Since they only execute during the build, and even then only when out
> >> of date, they can afford to be more complete tests (ie use plenty of cpu
> >> time)
> >> * If the code builds, you know all the unit tests pass. No need for a
> >> special unittest build and manual running of assorted programs to see if
> >> the tests pass.
> >> * No need for special builds with -unittest turned on.
> > 
> > Obviously, it wouldn't resolve all of your concerns, but I would point
> > out that you can use version(unittest) to enclose stuff that's only
> > supposed to be in the unit tests build. And that includes using
> > version(unittest) on imports, avoiding having to import stuff which is
> > only needed for unit tests during normal builds.
> > 
> > - Jonathan M Davis
> 
> That is a good point, but as you say, it doesn't address all the concerns.
> 
> I would be interested to hear some success stories for the
> unittest-keyword approach. So far I can't see any up-side.

Personally, I find the unit tests to be _way_ more maintainable when they're 
right next to the code. I _really_ like that aspect of how unit tests are done 
in D. However, it does mean that you have to dig through more code to get at 
the actual function definitions (especially if you're thorough with your unit 
tests), and it _does_ increase problems with cyclical dependencies if you need 
static constructors for your unit tests and don't normally have a static 
constructor in the module in question (though you can probably just use an 
extra unittest block at the top of the module to do what the static 
constructor would have done for the unit tests).

I have no problem with having to do a special build for the unit tests. That's 
what I've generally had to do with other unit test frameworks anyway. Also, 
I'd hate for the tests to run as part of the build. I can understand why you 
might want that, but it would really hurt flexibility when debugging unit 
tests. How could you run gdb (or any other debugger) on the unit tests if it 
never actually builds? It's _easy_ to use gdb on unit tests with how unit 
tests currently work in D.

Really, I see only three downsides to how unit tests currently work in D, and 
two of those should be quite fixable.

1. Unit tests don't have names, so it's a royal pain to figure out which test 
an exception escaped from when an exception escapes a unit test. Adding an 
optional syntax like unittest(testname) would solve that problem. It's been 
proposed before, and it's a completely backwards compatible change (as long as 
the names aren't required).

2. Once a unit test fails in a module, none of the remaining unittest blocks 
run. Every unittest in a module should run regardless of whether the previous 
ones succeeded. A particular unittest block should not continue after it has 
had a failure, but the succeeding unittest blocks should be run. This has been 
discussed before and it is intended that it will be how unit tests work 
eventually, but as I understand it, there are changes which must be made to 
the compiler before it can happen.

3. Having the unit tests in the module does make it harder to find the code in 
the module. Personally, I think that the increased ease of maintenance of 
having the unit tests right next to the functions that they go with outweighs 
this problem, but there are plenty of people that complain about the unit 
tests making it harder to sort through the actual code. With a decent editor 
though, it's easy to hop around in the code, skipping unit tests, and you can 
shrink the unit test blocks so that they aren't shown. So, while this is 
certainly a valid concern, I don't think that it's ultimately enough of an 
issue to merit changing how unit tests work in D.

I certainly won't claim that unit tests in D are perfect, but IMHO they are 
far superior to having to deal with an external framework such as JUnit or 
CPPUnit. They also work really well for code maintenance and are so easy to 
use, that not using them is practically a crime.

- Jonathan M Davis


More information about the Digitalmars-d mailing list