Dmitry Onoshko 0 Posted November 6 I guess, I know the answer but maybe I miss something. The question arose related to unit testing with DUnitX but is not really tied to it. Say, we have a Delphi unit shared between multiple projects in a project group, and certain code pieces in the unit are written in $IFDEFs to exclude them in one of the projects and include for the rest (say, for performance reasons or to remove some stuff in a program for outside world use). The code pieces in question are part of quite complex algorithm(s), so unit testing is a must. I understand one can just create another build configuration for the DUnitX project, or even create a separate test project, but that’s not quite convenient: being able to test the whole unit in all its variant with a single test project would be much better. Another use case I can imagine is something related to supporting different versions of some protocol or format in the same executable without the risk of introducing bugs by merging several implementations into a single one (although this could probably be done by giving unit for different versions different names). So, let’s just stick to testing $IFDEF-dependent unit as a whole in one test project as the use case. I guess, linker and separate unit compilation are one of the problems here, but… Is that even possible? Share this post Link to post
dummzeuch 1505 Posted November 6 You could use includes from different units for testing. Lets say this is the unit you want to test: {$IFNDEF UNIT_TESTING} unit ToTest; interface uses Whateever, isNecesssary; function AlwaysAvailable(): TSomeType; function NotAlwaysAvaialble(): TSomeOtherType; implementation uses Other, Units; {$ENDIF} function AlwaysAvailable(): TSomeType; begin // implementation here end; {$IFDEF USE_EVERYTHING} function NotAlwaysAvaialble(): TSomeOtherType; begin // implementation here end; {$ENDIF} {$IFNDEF UNIT_TESTING} end. {$ENDIF} Instead of directly using this unit in the unit tests, you use two (or more) different units that include that unit, e.g.: unit UnitForTesting1; interface uses Whateever, isNecesssary; function AlwaysAvailable(): TSomeType; function NotAlwaysAvaialble(): TSomeOtherType; implementation uses Other, Units; implementation {$DEFINE UNIT_TESTING} {$DEFINE USE_EVERYTHING} {$I 'ToTest'} end. and unit UnitForTesting2; interface uses Whateever, isNecesssary; function AlwaysAvailable(): TSomeType; implementation uses Other, Units; implementation {$DEFINE UNIT_TESTING} {$I 'ToTest'} end. I haven't tested this, but it should work. On the other hand, this makes the testing code quite confusing and might lead to code not being tested as intended. Share this post Link to post
Dmitry Onoshko 0 Posted November 6 2 minutes ago, dummzeuch said: You could use includes from different units for testing. Lets say this is the unit you want to test: {$IFNDEF UNIT_TESTING} unit ToTest; interface uses Whateever, isNecesssary; function AlwaysAvailable(): TSomeType; function NotAlwaysAvaialble(): TSomeOtherType; implementation uses Other, Units; {$ENDIF} function AlwaysAvailable(): TSomeType; begin // implementation here end; {$IFDEF USE_EVERYTHING} function NotAlwaysAvaialble(): TSomeOtherType; begin // implementation here end; {$ENDIF} {$IFNDEF UNIT_TESTING} end. {$ENDIF} Instead of directly using this unit in the unit tests, you use two (or more) different units that include that unit, e.g.: unit UnitForTesting1; interface uses Whateever, isNecesssary; function AlwaysAvailable(): TSomeType; function NotAlwaysAvaialble(): TSomeOtherType; implementation uses Other, Units; implementation {$DEFINE UNIT_TESTING} {$DEFINE USE_EVERYTHING} {$I 'ToTest'} end. and unit UnitForTesting2; interface uses Whateever, isNecesssary; function AlwaysAvailable(): TSomeType; implementation uses Other, Units; implementation {$DEFINE UNIT_TESTING} {$I 'ToTest'} end. I haven't tested this, but it should work. On the other hand, this makes the testing code quite confusing and might lead to code not being tested as intended. I feel $INCLUDEing full units in an implementation section might not work, but I think I get the idea. And I’d agree with you that this would make it trickier than it should as the unit should have been torn into pieces for $INCLUDEing, all that only to test it. Share this post Link to post
Rollo62 536 Posted November 6 I like to separate different aspects of stuff in completely different units, which where combined then in a wrapper unit, like so unit DecideWhichUnitInPlace; interface uses // This is the only place where separation takes place {$IFDEF UNIT_TESTING} UnitWithTests, {$ELSE } UnitWithoutTests {$ENDIF } ; //Even separate classes and interfaces like that, not only functions type // This can point to completely different, but cleanly separated versions, depending on defines {$IFDEF UNIT_TESTING} TMyType = UnitWithTests.TMyType; {$ELSE } TMyType = UnitWithoutTests.TMyType; {$ENDIF } ; implementation Then no defines are needed in those units at all unit UnitWithTest; interface uses Whateever, isNecesssary; function AlwaysAvailable(): TSomeType; function NotAlwaysAvaialble(): TSomeOtherType; type TMyType = class function AlwaysAvailable(): TSomeType; function NotAlwaysAvaialble(): TSomeOtherType; end implementation uses Other, Units; function AlwaysAvailable(): TSomeType; begin // implementation here end; function NotAlwaysAvaialble(): TSomeOtherType; begin // implementation here end; function TMyType.AlwaysAvailable(): TSomeType; begin // call AlwaysAvailable; end; function TMyType.NotAlwaysAvaialble(): TSomeOtherType; begin // call NotAlwaysAvaialble; end; end. This is not the beest example to explain this and it depends highly on what you really want to achieve. Maybe it gives you some helpful thoughts. unit UnitWithoutTest; interface uses Whateever; function AlwaysAvailable(): TSomeType; //function NotAlwaysAvaialble(): TSomeOtherType; type TMyType = class function AlwaysAvailable(): TSomeType; function NotAlwaysAvaialble(): TSomeOtherType; end implementation uses Other, Units; function AlwaysAvailable(): TSomeType; begin // implementation here end; //function NotAlwaysAvaialble(): TSomeOtherType; //begin // // NO implementation here, or completely removed //end; function TMyType.AlwaysAvailable(): TSomeType; begin // call AlwaysAvailable; end; function TMyType.NotAlwaysAvaialble(): TSomeOtherType; begin // call, ignore or mock, when function is NOT available //NotAlwaysAvaialble; end; Share this post Link to post
dummzeuch 1505 Posted November 7 14 hours ago, Dmitry Onoshko said: I feel $INCLUDEing full units in an implementation section might not work, but I think I get the idea. Including a full unit will definitely work, as long as it contains ifdefs that hide parts of it from the compiler. I have used this trick for many years to create pseudo templates long before Delphi had generics. The linked blog post explains it in depth. Share this post Link to post