I am trying to add my first unit test to an existing Open Source project. Specifically, I added a new class, called audio_manager:
src/audio/audio_manager.h
src/audio/audio_manager.cc
I created a src/test directory structure that mirrors the structure of the implementation files, and wrote my googletest unit tests:
src/test/audio/audio_manager.cc
Now, I am trying to set up my Makefile.am to compile and run the unit test:
src/test/audio/Makefile.am
I copied Makefile.am from:
src/audio/Makefile.am
Does anyone have a simple recipe for me, or is it to the cryptic automake documentation for me? :)
If the existing project already has a test structure in place, then you should just add:
TESTS += audio_manager
to the existing tests/Makefile.am. If the existing project does not have a test structure in place, you should run screaming for the hills.
If running for the hills is not acceptable, there's a fair bit of work in getting the test structure in place, but it's not insurmountable. You might prefer to make tests a sibling of src, but that's not necessary. It's probably easier to start with a fresh Makefile.am rather than copying the Makefile.am from src, but maybe not. Possibly, all you'll need to do is change lines of the form:
bin_PROGRAMS = ...
to
check_PROGRAMS = ...
add the line
TESTS = test-audio-manager
change the name of audio_manager.cc to test-audio-manager.cc (that's not strictly necessary, but will help maintainability. I changed _ to - purely out of personal preference) and add a
SUBDIRS = tests/audio
to src/Makefile.am. (If there's already a SUBDIRS directive, append to that assignment or use +=)
William's answer got me where I needed to go. Just for the sake of the community, here's what I ended up doing:
I moved my tests back into the main directory structure and prepended test_, as per William's suggestions.
I added a few lines to src/audio/Makefile.am to enable unit tests:
# Unit tests
noinst_PROGRAMS = test_audio_manager
test_audio_manager_SOURCES = $(libadonthell_audio_la_SOURCES) test_audio_manager.cc
test_audio_manager_CXXFLAGS = $(libadonthell_audio_la_CXXFLAGS)
test_audio_manager_LDADD = $(libadonthell_audio_la_LIBADD) -lgtest
TESTS = test_audio_manager
Now, running "make check" fires the unit tests!
All of this can be seen here: http://github.com/ksterker/adonthell/commit/aacdb0fe22f59e61ef0f5986827af180c56ae9f3
Complimenting the information in the other answers, you can also specify multiple tests to TESTS.
Regardless of how many tests you specify, you don't actually have to specify them twice, instead just set TESTS to $(check_PROGRAMS) - this can help to prevent an accidental situation of adding your test to check_PROGRAMS but forgetting to add it to TESTS, causing your new test to be added to the build, but never being run by make check:
# Unit tests
check_PROGRAMS = test_audio_manager
test_audio_manager_SOURCES = test_audio_manager.cc
TESTS = $(check_PROGRAMS)
...or to do the same with multiple tests:
# Unit tests
check_PROGRAMS = test_audio_manager test_video_manager
test_audio_manager_SOURCES = test_audio_manager.cc
test_video_manager_SOURCES = test_video_manager.cc
TESTS = $(check_PROGRAMS)
Related
The documentation for Rust tests indicates that unit tests are run when the module that defines them is in scope, with the test configuration flag activated:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
In a large project, however, it is frequent to confine a test module to a different file, so that the test module is pointed to differently:
#[cfg(test)]
#[path = "unit_tests/tests.rs"]
mod tests;
With this setup, in a large project, it may happen that someone deletes the reference to src/unit_tests/tests.rs (i.e. the code block above), without deleting the test file itself. This usually indicates a bug:
either the removal was intentional, and the file src/unit_tests/tests.rs should not remain in the repository,
or it wasn't, in which case the tests in src/unit_tests/tests.rs are meant to be run and aren't, and CI should be vocal about that.
I'd like to add a verification that there are no such unmoored test files in my crate, to the CI of my project. Is there any tooling in the rust ecosystem that could help me detect those unlinked test files programmatically?
(integration tests are automatically compiled if found in the tests/ repository of the crate, but IIUC, that does not apply to unit tests)
There is an issue asking for cargo to do this check, cargo (publish) should complain about unused source files, and from that found the cargo-modules crate can do this. For example:
//! src/lib.rs
#[cfg(test)]
mod tests;
//! src/tests.rs
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
> cargo modules generate tree --lib --with-orphans --cfg-test
crate mycrate
└── mod tests: pub(crate) #[cfg(test)]
However, if I remove the #[cfg(test)] mod tests; the output would look like this:
> cargo modules generate tree --lib --with-orphans --cfg-test
crate mycrate
└── mod tests: orphan
You would be able to grep for "orphan" and fail your check based on that.
I will say though that this tool seems very slow from my small tests. The discussion in the linked issue indicate that a better way would have to be used if implemented in cargo.
You will also have to keep in mind any other conditional compilation may yield false positives (i.e. you legitimately include or exclude certain modules based on architecture, OS, or features). There is an --all-features flag that could help with some of that.
There is a very old rustc issue on the subject, which remains open.
However one of the commenters provides a hack:
One possible way to implement this (including as a third-party tool) is to parse the .d files rustc emits for cargo, and look for any .rs files that aren't mentioned.
rg --no-filename '^[^/].*\.rs:$' target/debug/deps/*.d | sed 's/:$//' | sort -u | diff - <(fd '\.rs$' | sort -u)
Apparently the .d files are
makefile-compatible dependency lists
and so I assume they should list the relationship between all rust files in the project. Which is why if one is missing, it's not seen by cargo / rustc.
Is there an established best practice for separating unit tests and integration tests in GoLang (testify)? I have a mix of unit tests (which do not rely on any external resources and thus run really fast) and integration tests (which do rely on any external resources and thus run slower). So, I want to be able to control whether or not to include the integration tests when I say go test.
The most straight-forward technique would seem to be to define a -integrate flag in main:
var runIntegrationTests = flag.Bool("integration", false
, "Run the integration tests (in addition to the unit tests)")
And then to add an if-statement to the top of every integration test:
if !*runIntegrationTests {
this.T().Skip("To run this test, use: go test -integration")
}
Is this the best I can do? I searched the testify documentation to see if there is perhaps a naming convention or something that accomplishes this for me, but didn't find anything. Am I missing something?
#Ainar-G suggests several great patterns to separate tests.
This set of Go practices from SoundCloud recommends using build tags (described in the "Build Constraints" section of the build package) to select which tests to run:
Write an integration_test.go, and give it a build tag of integration. Define (global) flags for things like service addresses and connect strings, and use them in your tests.
// +build integration
var fooAddr = flag.String(...)
func TestToo(t *testing.T) {
f, err := foo.Connect(*fooAddr)
// ...
}
go test takes build tags just like go build, so you can call go test -tags=integration. It also synthesizes a package main which calls flag.Parse, so any flags declared and visible will be processed and available to your tests.
As a similar option, you could also have integration tests run by default by using a build condition // +build !unit, and then disable them on demand by running go test -tags=unit.
#adamc comments:
For anyone else attempting to use build tags, it's important that the // +build test comment is the first line in your file, and that you include a blank line after the comment, otherwise the -tags command will ignore the directive.
Also, the tag used in the build comment cannot have a dash, although underscores are allowed. For example, // +build unit-tests will not work, whereas // +build unit_tests will.
To elaborate on my comment to #Ainar-G's excellent answer, over the past year I have been using the combination of -short with Integration naming convention to achieve the best of both worlds.
Unit and Integration tests harmony, in the same file
Build flags previously forced me to have multiple files (services_test.go, services_integration_test.go, etc).
Instead, take this example below where the first two are unit tests and I have an integration test at the end:
package services
import "testing"
func TestServiceFunc(t *testing.T) {
t.Parallel()
...
}
func TestInvalidServiceFunc3(t *testing.T) {
t.Parallel()
...
}
func TestPostgresVersionIntegration(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
...
}
Notice the last test has the convention of:
using Integration in the test name.
checking if running under -short flag directive.
Basically, the spec goes: "write all tests normally. if it is a long-running tests, or an integration test, follow this naming convention and check for -short to be nice to your peers."
Run only Unit tests:
go test -v -short
this provides you with a nice set of messages like:
=== RUN TestPostgresVersionIntegration
--- SKIP: TestPostgresVersionIntegration (0.00s)
service_test.go:138: skipping integration test
Run Integration Tests only:
go test -run Integration
This runs only the integration tests. Useful for smoke testing canaries in production.
Obviously the downside to this approach is if anyone runs go test, without the -short flag, it will default to run all tests - unit and integration tests.
In reality, if your project is large enough to have unit and integration tests, then you most likely are using a Makefile where you can have simple directives to use go test -short in it. Or, just put it in your README.md file and call it the day.
I see three possible solutions. The first is to use the short mode for unit tests. So you would use go test -short with unit tests and the same but without the -short flag to run your integration tests as well. The standard library uses the short mode to either skip long-running tests, or make them run faster by providing simpler data.
The second is to use a convention and call your tests either TestUnitFoo or TestIntegrationFoo and then use the -run testing flag to denote which tests to run. So you would use go test -run 'Unit' for unit tests and go test -run 'Integration' for integration tests.
The third option is to use an environment variable, and get it in your tests setup with os.Getenv. Then you would use simple go test for unit tests and FOO_TEST_INTEGRATION=true go test for integration tests.
I personally would prefer the -short solution since it's simpler and is used in the standard library, so it seems like it's a de facto way of separating/simplifying long-running tests. But the -run and os.Getenv solutions offer more flexibility (more caution is required as well, since regexps are involved with -run).
I was trying to find a solution for the same recently.
These were my criteria:
The solution must be universal
No separate package for integration tests
The separation should be complete (I should be able to run integration tests only)
No special naming convention for integration tests
It should work well without additional tooling
The aforementioned solutions (custom flag, custom build tag, environment variables) did not really satisfy all the above criteria, so after a little digging and playing I came up with this solution:
package main
import (
"flag"
"regexp"
"testing"
)
func TestIntegration(t *testing.T) {
if m := flag.Lookup("test.run").Value.String(); m == "" || !regexp.MustCompile(m).MatchString(t.Name()) {
t.Skip("skipping as execution was not requested explicitly using go test -run")
}
t.Parallel()
t.Run("HelloWorld", testHelloWorld)
t.Run("SayHello", testSayHello)
}
The implementation is straightforward and minimal. Although it requires a simple convention for tests, but it's less error prone. Further improvement could be exporting the code to a helper function.
Usage
Run integration tests only across all packages in a project:
go test -v ./... -run ^TestIntegration$
Run all tests (regular and integration):
go test -v ./... -run .\*
Run only regular tests:
go test -v ./...
This solution works well without tooling, but a Makefile or some aliases can make it easier to user. It can also be easily integrated into any IDE that supports running go tests.
The full example can be found here: https://github.com/sagikazarmark/modern-go-application
I encourage you to look at Peter Bourgons approach, it is simple and avoids some problems with the advice in the other answers: https://peter.bourgon.org/blog/2021/04/02/dont-use-build-tags-for-integration-tests.html
There are many downsides to using build tags, short mode or flags, see here.
I would recommend using environment variables with a test helper that can be imported into individual packages:
func IntegrationTest(t *testing.T) {
t.Helper()
if os.Getenv("INTEGRATION") == "" {
t.Skip("skipping integration tests, set environment variable INTEGRATION")
}
}
In your tests you can now easily call this at the start of your test function:
func TestPostgresQuery(t *testing.T) {
IntegrationTest(t)
// ...
}
Why I would not recommend using either -short or flags:
Someone who checks out your repository for the first time should be able to run go test ./... and all tests are passing which is often not the case if this relies on external dependencies.
The problem with the flag package is that it will work until you have integration tests across different packages and some will run flag.Parse() and some will not which will lead to an error like this:
go test ./... -integration
flag provided but not defined: -integration
Usage of /tmp/go-build3903398677/b001/foo.test:
Environment variables appear to be the most flexible, robust and require the least amount of code with no visible downsides.
Preface
I'm new to Unit Testing and rather new to C++. Recently I had some experience with test driven development in Ruby using RSpec. Now I'm trying getting similar working in C++ with Boost's unit testing framework.
Scenario
I'm organising my header and source files of the application in a directory /src under the project's root folder. As I've seen it multiple times around in other C++ programs, having the tests in a directory /tests in the project's root directory seems reasonable.
Now I want to replicate the directory structure of the source files as well in the tests. Thus, assume I've got the following source/header file structure:
/src
/controller
controller_class.h
controller_class.cpp
/model
model_a.h
model_a.cpp
model_b.h
model_b.cpp
/view
simple_view.h
simple_view.cpp
And thus the tests are organised as followed
/tests
TestRunner.cpp
/controller
controller_class_test.cpp
/model
model_a_test.cpp
model_b_test.cpp
/view
simple_view_test.cpp
For the TestRunner.cpp I took the example from this blog post:
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE "MyProgram Unit Tests"
#include <boost/test/unit_test.hpp>
Problem
I now thought to continue in TestRunner.cpp with the creation of the basic test suits (for controller, model and view) as follows
BOOST_AUTO_TEST_SUITE ( controller )
//some stuff here
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE ( model )
//some stuff here
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE ( view )
//some stuff here
BOOST_AUTO_TEST_SUITE_END()
Desire
But how can I now integrate further nested test suites and cases into these top-level test suits? I finally want to have the actual test cases only appear in the *_test.cpp files. While each of those files wrap the test cases into one additional test suite:
Master Test Module (TestRunner.cpp)
Controller Test Suite (TestRunner.cpp)
Controller Class Test Suite (controller_class_test.cpp)
Controller Class Test Cases (controller_class_test.cpp)
Model Test Suite (TestRunner.cpp)
Model A Test Suite (model_a_test.cpp)
Model A Test Cases (model_a_test.cpp)
Model B Test Suite (model_b_test.cpp)
Model B Test Cases (model_B_test.cpp)
View Test Suite (TestRunner.cpp)
Simple View Test Suite (simple_view_test.cpp)
Simple View Test Cases (simple_view_test.cpp)
Question
How do I have to include the nested suits and cases in the respective higher level suite? I could not find anything in the Boost documentation, though The Unit Test Framework > User's guide > Test organization > Test suite > Automated registration came pretty close.
In Ruby with RSpec one just needs to place the test files (read: *_spec.rb) the way I did and its automatically iterating through them. I guess, with Boost I have to explicitly declare it that way.
Boost.Test test suites are similar to C++ namespaces. You can restart them any time. Each of your test files just need to place test cases in correct test suites:
controller_class_a_test.cpp:
BOOST_AUTO_TEST_SUITE( controller )
BOOST_AUTO_TEST_CASE( test_class_a )
{
}
BOOST_AUTO_TEST_SUITE_END()
controller_class_b_test.cpp:
BOOST_AUTO_TEST_SUITE( controller )
BOOST_AUTO_TEST_CASE( test_class_b )
{
}
BOOST_AUTO_TEST_SUITE_END()
The same concept applies to the test tree of any depth. Also you do not need top level TestRunner.cpp at all. Just combine all your test files into a single test module and Boost.Test will take care about the rest.
I've been following Apple's documentation on writing unit tests for the iPhone, and out of the box, the routine doesn't seem to work. I'm not sure I understand where the unit test is going to get the reference to the application delegate.
My Dependencies are like the following:
My_Program_target -> UnitTesting_target -> UnitTests_bundle
The following code snippet is where the assert fails. I'm very familiar with CPPUNIT, but I'm having trouble understanding how this crosses over.
- (void) testAppDelegate {
id yourApplicationDelegate = [[UIApplication sharedApplication] delegate];
STAssertNotNil(yourApplicationDelegate, #"UIAppliation failed to find the AppDelegate");
}
Additionally:
I've decided in my approach to do a logic test. I'm trying to read in an XML file, but I'm not having luck resolving the bundle, which will provide me with the path by which I can access my file. I've tried pasting in the path output by allBundles, but that path doesn't seem to work either. Below is what I'm executing in my test (you can see the debug statement I'm using to output the paths of the bundles):
NSLog(#"BundlePaths: %#", [NSBundle allBundles]);
NSString * path = [[NSBundle bundleWithPath:#"$(TARGET_BUILD_DIR)"] pathForResource:#"SimpleTestList" ofType:#"plist"];
STAssertNotNil(path, #"Bundle Location couldn't find the file specified");
Essentially, the assert on path is not successful, but I'm not sure what to put for the path or directory to reference my unitTest bundle that I've told to copy the bundle resources. Calling [NSBundle mainBundle] does not work either.
Ok, so I've figured it out. In order to open a file in a unit test, you'll need to specify the file to open as:
NSString * filePath = [[NSBundle bundleForClass:[self class] ] pathForResource:#"SimpleTestList" ofType:#"plist"];
If you include this in a class that's compiled as part of your unit test bundle, that class will look inside the unit test bundle for the file SimpleTestList.plist.
For a unit test, just make sure you set up "Copy Bundle Resources" to include your plist in your unit test bundle.
If you need the application delegate, you have to run the unit tests on the device itself and not the simulator. Also, you will see unit test output appear in the console, not in the build results.
The key thing to know is that there are two types of unit tests - logic tests that are run outside of the executable, and then integrated system kinds of tests that need the full running environment.
The logic tests MUST be run with the simulator selected as the target or they will not run.
The integrated system tests MUST be run as part of the executable, on the device - you'll want a new target to accomplish this.
Sorry this is all so complex, this aspect is still very much a work in progress compared to many other unit testing frameworks.
The Swift 3 translation of Gary's answer above (using an URL instead of a string path) is:
let url = Bundle(for: type(of: self)).url(forResource: "SimpleTestList", withExtension: "plist")
Notice the critical and non-obvious part type(of: self) instead of [self class].
I am writing a component that, given a ZIP file, needs to:
Unzip the file.
Find a specific dll among the unzipped files.
Load that dll through reflection and invoke a method on it.
I'd like to unit test this component.
I'm tempted to write code that deals directly with the file system:
void DoIt()
{
Zip.Unzip(theZipFile, "C:\\foo\\Unzipped");
System.IO.File myDll = File.Open("C:\\foo\\Unzipped\\SuperSecret.bar");
myDll.InvokeSomeSpecialMethod();
}
But folks often say, "Don't write unit tests that rely on the file system, database, network, etc."
If I were to write this in a unit-test friendly way, I suppose it would look like this:
void DoIt(IZipper zipper, IFileSystem fileSystem, IDllRunner runner)
{
string path = zipper.Unzip(theZipFile);
IFakeFile file = fileSystem.Open(path);
runner.Run(file);
}
Yay! Now it's testable; I can feed in test doubles (mocks) to the DoIt method. But at what cost? I've now had to define 3 new interfaces just to make this testable. And what, exactly, am I testing? I'm testing that my DoIt function properly interacts with its dependencies. It doesn't test that the zip file was unzipped properly, etc.
It doesn't feel like I'm testing functionality anymore. It feels like I'm just testing class interactions.
My question is this: what's the proper way to unit test something that is dependent on the file system?
edit I'm using .NET, but the concept could apply Java or native code too.
Yay! Now it's testable; I can feed in test doubles (mocks) to the DoIt method. But at what cost? I've now had to define 3 new interfaces just to make this testable. And what, exactly, am I testing? I'm testing that my DoIt function properly interacts with its dependencies. It doesn't test that the zip file was unzipped properly, etc.
You have hit the nail right on its head. What you want to test is the logic of your method, not necessarily whether a true file can be addressed. You don´t need to test (in this unit test) whether a file is correctly unzipped, your method takes that for granted. The interfaces are valuable by itself because they provide abstractions that you can program against, rather than implicitly or explicitly relying on one concrete implementation.
Your question exposes one of the hardest parts of testing for developers just getting into it:
"What the hell do I test?"
Your example isn't very interesting because it just glues some API calls together so if you were to write a unit test for it you would end up just asserting that methods were called. Tests like this tightly couple your implementation details to the test. This is bad because now you have to change the test every time you change the implementation details of your method because changing the implementation details breaks your test(s)!
Having bad tests is actually worse than having no tests at all.
In your example:
void DoIt(IZipper zipper, IFileSystem fileSystem, IDllRunner runner)
{
string path = zipper.Unzip(theZipFile);
IFakeFile file = fileSystem.Open(path);
runner.Run(file);
}
While you can pass in mocks, there's no logic in the method to test. If you were to attempt a unit test for this it might look something like this:
// Assuming that zipper, fileSystem, and runner are mocks
void testDoIt()
{
// mock behavior of the mock objects
when(zipper.Unzip(any(File.class)).thenReturn("some path");
when(fileSystem.Open("some path")).thenReturn(mock(IFakeFile.class));
// run the test
someObject.DoIt(zipper, fileSystem, runner);
// verify things were called
verify(zipper).Unzip(any(File.class));
verify(fileSystem).Open("some path"));
verify(runner).Run(file);
}
Congratulations, you basically copy-pasted the implementation details of your DoIt() method into a test. Happy maintaining.
When you write tests you want to test the WHAT and not the HOW.
See Black Box Testing for more.
The WHAT is the name of your method (or at least it should be). The HOW are all the little implementation details that live inside your method. Good tests allow you to swap out the HOW without breaking the WHAT.
Think about it this way, ask yourself:
"If I change the implementation details of this method (without altering the public contract) will it break my test(s)?"
If the answer is yes, you are testing the HOW and not the WHAT.
To answer your specific question about testing code with file system dependencies, let's say you had something a bit more interesting going on with a file and you wanted to save the Base64 encoded contents of a byte[] to a file. You can use streams for this to test that your code does the right thing without having to check how it does it. One example might be something like this (in Java):
interface StreamFactory {
OutputStream outStream();
InputStream inStream();
}
class Base64FileWriter {
public void write(byte[] contents, StreamFactory streamFactory) {
OutputStream outputStream = streamFactory.outStream();
outputStream.write(Base64.encodeBase64(contents));
}
}
#Test
public void save_shouldBase64EncodeContents() {
OutputStream outputStream = new ByteArrayOutputStream();
StreamFactory streamFactory = mock(StreamFactory.class);
when(streamFactory.outStream()).thenReturn(outputStream);
// Run the method under test
Base64FileWriter fileWriter = new Base64FileWriter();
fileWriter.write("Man".getBytes(), streamFactory);
// Assert we saved the base64 encoded contents
assertThat(outputStream.toString()).isEqualTo("TWFu");
}
The test uses a ByteArrayOutputStream but in the application (using dependency injection) the real StreamFactory (perhaps called FileStreamFactory) would return FileOutputStream from outputStream() and would write to a File.
What was interesting about the write method here is that it was writing the contents out Base64 encoded, so that's what we tested for. For your DoIt() method, this would be more appropriately tested with an integration test.
There's really nothing wrong with this, it's just a question of whether you call it a unit test or an integration test. You just have to make sure that if you do interact with the file system, there are no unintended side effects. Specifically, make sure that you clean up after youself -- delete any temporary files you created -- and that you don't accidentally overwrite an existing file that happened to have the same filename as a temporary file you were using. Always use relative paths and not absolute paths.
It would also be a good idea to chdir() into a temporary directory before running your test, and chdir() back afterwards.
I am reticent to pollute my code with types and concepts that exist only to facilitate unit testing. Sure, if it makes the design cleaner and better then great, but I think that is often not the case.
My take on this is that your unit tests would do as much as they can which may not be 100% coverage. In fact, it may only be 10%. The point is, your unit tests should be fast and have no external dependencies. They might test cases like "this method throws an ArgumentNullException when you pass in null for this parameter".
I would then add integration tests (also automated and probably using the same unit testing framework) that can have external dependencies and test end-to-end scenarios such as these.
When measuring code coverage, I measure both unit and integration tests.
There's nothing wrong with hitting the file system, just consider it an integration test rather than a unit test. I'd swap the hard coded path with a relative path and create a TestData subfolder to contain the zips for the unit tests.
If your integration tests take too long to run then separate them out so they aren't running as often as your quick unit tests.
I agree, sometimes I think interaction based testing can cause too much coupling and often ends up not providing enough value. You really want to test unzipping the file here not just verify you are calling the right methods.
One way would be to write the unzip method to take InputStreams. Then the unit test could construct such an InputStream from a byte array using ByteArrayInputStream. The contents of that byte array could be a constant in the unit test code.
This seems to be more of an integration test as you are depending on a specific detail (the file system) that could change, in theory.
I would abstract the code that deals with the OS into it's own module (class, assembly, jar, whatever). In your case you want to load a specific DLL if found, so make an IDllLoader interface and DllLoader class. Have your app acquire the DLL from the DllLoader using the interface and test that .. you're not responsible for the unzip code afterall right?
Assuming that "file system interactions" are well tested in the framework itself, create your method to work with streams, and test this. Opening a FileStream and passing it to the method can be left out of your tests, as FileStream.Open is well tested by the framework creators.
You should not test class interaction and function calling. instead you should consider integration testing. Test the required result and not the file loading operation.
As others have said, the first is fine as an integration test. The second tests only what the function is supposed to actually do, which is all a unit test should do.
As shown, the second example looks a little pointless, but it does give you the opportunity to test how the function responds to errors in any of the steps. You don't have any error checking in the example, but in the real system you may have, and the dependency injection would let you test all the responses to any errors. Then the cost will have been worth it.
For unit test I would suggest that you include the test file in your project(EAR file or equivalent) then use a relative path in the unit tests i.e. "../testdata/testfile".
As long as your project is correctly exported/imported than your unit test should work.