The head lines are essentially a non-problem, so I don't see a need to discuss it.
If you want your program to be able to handle streaming data, there's no way around buffering. But if you only want to be able to read true files, you can position to the end minus some buffer size, and read a buffer to count line endings in the buffer. If there are not ...
One approach is to use parametized or dynamic tests. You potentially have just one test that the test framework calls multiple times with a different data set each time that exercises different paths through your code.
So as a contrived example, I might test my Plus function multiple times like this:
[TestCase(1, 1, 2)]
[TestCase(0, 0, 0)]
[TestCase(0, -1, -...