unittest which uses a disk file

H. S. Teoh hsteoh at quickfur.ath.cx
Wed Jan 16 21:32:19 UTC 2019


On Wed, Jan 16, 2019 at 09:07:24PM +0000, Victor Porton via Digitalmars-d-learn wrote:
> What is the rule for unittest which uses a file (containing example
> data for testing) available only in the source distribution, not in
> binary distribution?
> 
> I am writing a library.
> 
> The library has also a file main.d which is compiled only in DUB
> "application" configuration (I use this configuration solely for
> testing.)
> 
> Maybe I should put unittest { } into main.d not in the module which I
> test?
> 
> Also, what is the correct way to locate the file in the filesystem?

There is no rule about this as far as I know, but generally speaking, my
advice is to avoid touching the filesystem from inside a unittest.  This
is not always possible, but where possible, I highly recommend
templatizing the File type so that you can substitute it with an
in-memory-only proxy in your unittest.  For example, instead of:

	auto myFunc(Args...)(File fp, Args args) {
		...
		fp.rawWrite(...);
		fp.rawRead(...);
		... // etc.
	}

	unittest
	{
		auto testfile = File("testfile", "r+"); // <-- ugh
		auto r = myFunc(testfile, ...);
		...
	}

do something like this instead:

	auto myFunc(File = std.stdio.File, Args...)(File fp, Args args) {
		...
		fp.rawWrite(...);
		fp.rawRead(...);
		... // etc.
	}

	unittest
	{
		struct FakeFile {
			string fakedata = "blah blah blah";
			void[] rawRead(...) {
				// copy fakedata into output buffer,
				// etc.
			}
			... // ditto for any other method you might use
		}
		FakeFile testfile; // N.B.: no actual filesystem access
		auto r = myFunc(testfile, ...);
		...
	}

This way, you can verify your code logic using only in-memory test data,
and you don't have to worry about polluting the filesystem with
temporary files, cleaning up after your unittest is done, setting up
paths, and all that messy stuff.

Better yet, separate your code logic so that accessing the filesystem
only happens in one place (e.g., in a user-facing API that takes
filenames, say), and encapsulate the file data as a generic data source,
so that the main logic of your code operates on the generic data source
(e.g., an input range of chars or bytes, or whatever other structure
most convenient for your logic) without any specific binding to
std.stdio.File.  Then it will be easy to pass in test data to your core
logic without needing to use File or the FakeFile hack above.

(The FakeFile hack is really only for testing low-level functions that
are actually intended to interact directly with the filesystem;
generally, I try to structure my code so that the "business logic" is
independent of the filesystem. It just operates on whatever abstract
data source is most convenient, whether a range, or even just a string
buffer, or whatever. This makes it easy to test with non-file data, and
avoids importing std.stdio everywhere (which IMO is a code smell).  It
also makes it easier to extend to other data sources in the future, like
network data.)


T

-- 
Those who don't understand Unix are condemned to reinvent it, poorly.


More information about the Digitalmars-d-learn mailing list