views:

340

answers:

3

I have the following code for adding to/extracting from Zip. I'm trying to refactor this to make it test-ready. Can someone provide pointers on how I can accomplish this? Aside: I'm using Moq as my mock framework and MSTest as my Unit Testing tool

private const long BufferSize = 4096;

public static void ExtractZip(string zipFilename, string folder) {
  using (var zip = System.IO.Packaging.Package.Open(zipFilename, FileMode.OpenOrCreate)) {
    foreach (var part in zip.GetParts()) {
      using (var reader = new StreamReader(part.GetStream(FileMode.Open, FileAccess.Read))) {
        using (var writer = new FileStream(folder + "\\" + Path.GetFileName(part.Uri.OriginalString),
                                           FileMode.Create, FileAccess.Write)) {
          var buffer = System.Text.Encoding.UTF8.GetBytes(reader.ReadToEnd());
          writer.Write(buffer, 0, buffer.Length);
        }
      }
    }
  }
}

public static void AddFileToZip(string zipFilename, string fileToAdd) {
  using (var zip = System.IO.Packaging.Package.Open(zipFilename, FileMode.OpenOrCreate)) {
    var destFilename = ".\\" + Path.GetFileName(fileToAdd);
    var uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
    if (zip.PartExists(uri)) {
      zip.DeletePart(uri);
    }
    var part = zip.CreatePart(uri, "", CompressionOption.Normal);
    using (var fileStream = new FileStream(fileToAdd, FileMode.Open, FileAccess.Read)) {
      using (var dest = part.GetStream()) {
        CopyStream(fileStream, dest);
      }
    }
  }
}

Thanks in advance.

A: 

Abstract away the creation of FileStreams behind IStreamProvider and pass it to AddFileToZip and ExtractZip. You'll have to abstract filesystem operations unless you're willing to do disk IO while doing unit tests.

Anton Gogolev
@Anton: Thanks for the reply. Yes, I realize that'd be a good start. But what about Opening the Zip file (requires me to have a physical archive :( )?
Vyas Bharghava
+1  A: 

I would make these two static methods (ExtractZip and AddFileToZip) instance methods, and put it into an interface:

public interface IZipper
{
  void ExtractZip(string zipFilename, string folder);
  void AddFileToZip(string zipFilename, string fileToAdd);
}

public class Zipper : IZipper
{
  public void ExtractZip(string zipFilename, string folder)
  { 
    //...
  }

  void AddFileToZip(string zipFilename, string fileToAdd)
  {
    //...
  }
}


// client code
class Foo
{
  private IZipper myZipper;
  // gets an instance of the zipper (injection), but implements only 
  // against the interface. Allows mocks on the IZipper interface.
  public Foo(IZipper zipper)
  {
    myZipper = zipper;
  }

}

Client code is now easy to test.

What about the Zipper class?

  • Consider if it is worth to test anyway.
  • In our project, we distinguish between unit tests (isolated) and integration tests, where it is possible to use the database or the file system. You could declare it as an "file system integration test". Of course, only the test target folder is allowed to be used. This shouldn't make any problems.
  • Consider to move the file operations out, and only work with streams. Then you can easily test the zipping on memory streams. The file operations still need to be somewhere and aren't tested.
Stefan Steinegger
@Stefan, thanks for reminding about the clients (of the zipping code). Yes, I'll mock the whole zipper for the clients. Thanks again for sharing your thoughts and helping out.
Vyas Bharghava
A: 
Vyas Bharghava