Unit Testing using Shims for ZipFile - c#

I'm attempting to unit test some code that use ZipFile.OpenRead within to extract some XML files from a ZIP (Writing the unit tests with moq)
Is there a way I can replace the call to ZipFile.OpenRead with my own result? I have used shims for similar situations, but I can't figure out what to do in this situation and documentation on shims is pretty sparse.
Here is (part of) the method that needs unit-testing:
public IEnumerable<ConfigurationViewModel> ExtractXmlFromZip(string fileName)
{
var configs = new List<ConfigurationViewModel>();
using (var archive = ZipFile.OpenRead(fileName))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
{
LoadConfigfromZipArchiveEntry(entry, configs)
}
}
}
return configs;
}

There isn't a way to mock a static class like ZipFile using mock, you would either wrap it using an IZipFileWrapper (say)
public IZipFileWrapper
{
ZipArchive OpenRead(string fileName)
}
public ZipFileWrapper : IZipFileWrapper
{
public ZipArchive OpenRead(string fileName)
{
return ZipFile.OpenRead(fileName)
}
}
Then the code becomes:
public MyObj
{
private IZipFileWrapper zipFileWrapper;
public MyObj(IZipFileWrapper zipFileWrapper)
{
this.zipFileWrapper = zipFileWrapper;
}
public IEnumerable<ConfigurationViewModel> ExtractXmlFromZip(string fileName)
{
var configs = new List<ConfigurationViewModel>();
// Call the wrapper
using (var archive = this.zipFileWrapper.OpenRead(fileName))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
{
LoadConfigfromZipArchiveEntry(entry, configs)
}
}
}
return configs;
}
}
And a test of
[TestMethod]
public void ExtractXmlFromZip_Test()
{
var myThing = new MyObj();
var fileName = "my.zip";
ZipArchive myZipArchive = CreateTestZipFile(); // Set up your return
var mockWrapper = new Mock<IZipFileWrapper>();
mockWrapper.Setup(m => m.OpenRead(fileName)).Returns(myZipArchive);
var configs = myThing.ExtractXmlFromZip(fileName);
// Assert
}
}
You would probably need to wrap more to get that passing, but hopefully that shows the concept.
(wrote this before I realised it was moq you were asking about and not shims from Microsoft Fakes)
There is an easier way to use Shims that can get to this code - from Microsoft Fakes.
The ZipFile class is part of System.IO.Compression.FileSystem, which is in the dll of the same name.
To let us use a ShimZipFile, we need to add a Fakes Assembly:
Note: We need to Fake System.IO.Compression.FileSystem (i.e. the dll) not System.IO.Compression dlll (which is the namespace of ZipFile).
Which should make the following changes to the project:
Then we can use it in a test such as:
[TestMethod]
public void ExtractXmlFromZip_Test()
{
var myThing = new MyObj();
var fileName = "my.zip";
ZipArchive myZipArchive = CreateTestZipFile(); // Set up your return
using (ShimsContext.Create())
{
System.IO.Compression.Fakes.ShimZipFile.OpenReadString = (filename) => myZipArchive;
var configs = myThing.ExtractXmlFromZip(fileName);
// Assert
}
}
There is info on MSDN about the naming conventions for shims that fakes generates.

One of the ways I have recently taken to is using a private delegated member on a class which has the single purpose of wrapping a concrete implementation.
Fortunately, for me, R# makes it easy to create a concrete proxy class with single private member, expose the public members on the wrapped type (through the Delegate Members code generator), then extract the interface to decouple, and provide ways to mock, and verify during testing.

Related

How do I resolve C# dependencies automatically?

I've been reading about Unity's dependency injection and I understand it's a thing and that it allows you to type a class to an interface. What I'm curious about is, do I HAVE to? In the below scenario there's a TerrainGenerator and TileCreator in the same space. How can I get the TileCreator within the generator as a dependency?
http://geekswithblogs.net/danielggarcia/archive/2014/01/23/introduction-to-dependency-injection-with-unity.aspx walks me through registering a type, but I read somewhere that as long as the class is visible in the Unity Assets section it'll be able to auto inject it, I just can't figure out the syntax (if it's possible).
Update
I could put all the classes in a single file... with a large system that could be pretty annoying. In the meantime it's an approach I'll try - better than having it not work at all.
update
Seems like Unity should be able to look at a class' constructor and perform these resolutions automatically and inject them in my class' constructor. Is that possible?
If you are looking for DI for the Unity3d engine, maybe this would work (I've not used it, but the feedback is positive) https://github.com/modesttree/Zenject
If you are talking about Microsoft's Unity DI library, you should be able to do this:
container.RegisterTypes(
AllClasses.FromLoadedAssemblies(),
WithMappings.FromMatchingInterface,
WithName.Default);
I always use the following code. When I load an application, the application looks in the directory for all Dlls. This way when you load a class with reflection it searches for the Dlls and exes. You can also add some more paths to search.
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(currentDomain_AssemblyResolve);
Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string defaultFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string assemblyName = new AssemblyName(args.Name).Name;
string assemblyNameDll = assemblyName + ".dll";
string assemblyNameExe = assemblyName + ".exe";
string assemblyPathDll = Path.Combine(defaultFolder, assemblyNameDll);
string assemblyPathExe = Path.Combine(defaultFolder, assemblyNameExe);
string assemblyPathToUse = null;
if (File.Exists(assemblyPathDll))
{
assemblyPathToUse = assemblyPathExe;
}
else if (File.Exists(assemblyPathExe))
{
assemblyPathToUse = assemblyPathExe;
}
else
{
IEnumerable<string> merge = AssemblyFolders.Values;
if (!string.IsNullOrEmpty(TempLoadingFolder))
{
merge = AssemblyFolders.Values.Union(new List<string>() { TempLoadingFolder });
}
foreach (var folder in merge)
{
assemblyPathDll = Path.Combine(folder, assemblyNameDll);
assemblyPathExe = Path.Combine(folder, assemblyNameExe);
if (File.Exists(assemblyPathDll))
{
assemblyPathToUse = assemblyPathDll;
break;
}
else if (File.Exists(assemblyPathExe))
{
assemblyPathToUse = assemblyPathExe;
break;
}
}
}
Assembly assembly = null;
if (assemblyPathToUse != null && File.Exists(assemblyPathToUse))
{
assembly = Assembly.LoadFrom(assemblyPathToUse);
}
return assembly;
}
Don't think it matters if you have the classes in the same file or not. Unity needs to know how to create the instance given the type.
If RegisterInstance is used, the specific object passed as argument is returned everytime Resolve is called for the type. If the type is registered using RegisterType (or not registered at all for concrete classes), Unity will try to instantiate the type by using the constructor with most number of arguments. For each of the parameter types, Unity will try to resolve them recursively.
Registering mappings for interface types to concrete types is mandatory but registering concrete types themselves is optional.
Sample code:
using Microsoft.Practices.Unity;
using System;
namespace Unity
{
interface IFooBar
{
string Message();
}
class Foo
{
string msg;
public Foo()
{
msg = "Hello";
}
public override string ToString()
{
return msg;
}
}
class Bar
{
private Foo _f;
private IFooBar _fb;
public Bar(Foo f, IFooBar fb)
{
this._f = f;
this._fb = fb;
}
public override string ToString()
{
return _f.ToString() + " World " + _fb.Message();
}
}
class FooBar : IFooBar
{
public string Message()
{
return "Unity!";
}
}
class Program
{
static void Main(string[] args)
{
UnityContainer container = new UnityContainer();
container.RegisterType<IFooBar, FooBar>(); // required
container.RegisterType<Foo>(); // optional
container.RegisterType<Bar>(); // optional
var mybar = container.Resolve<Bar>();
Console.WriteLine(mybar);
}
}
}
https://msdn.microsoft.com/en-us/library/microsoft.practices.unity.iunitycontainer_methods(v=pandp.20).aspx
No, you don't have to use interfaces, you can register and resolve concrete types as well.
For example, you can register the TerrainGenerator and TileCreator as follows:
var myTileCreator = new TileCreator();
container.RegisterType<TerrainGenerator>(new PerThreadLifetimeManager(), new InjectionFactory(c => new TerrainGenerator(myTileCreator)));
To resolve TerrainGenerator:
TerrainGenerator generator = container.Resolve<TerrainGenerator>();
To resolve TerrainGenerator with a different TileCreator:
TerrainGenerator generator = container.Resolve<TerrainGenerator>(new ParameterOverride("tileCreator", new TileCreator()));
You may want to read Dependency Injection with Unity - Patterns and Practices for more useful information like properties injection and alike.
Hope that helps.

How can I inject primitive values using RhinoAutoMocker?

I have been using RhinoAutoMocker for unit testing, and it works very well in almost all cases. I'm currently having trouble figuring out is how to use it when the Class Under Test has primitive constructor arguments.
Let's say I have two classes defined as such:
public class AddAnswerValidator : BaseValidator
{
public AddAnswerValidator(Answer answerToAdd,
IAnswerRepository answerRepository)
{
...some code
}
public override bool Validates()
{
...some code
}
}
public class RemoveAnswerValidator : BaseValidator
{
public RemoveAnswerValidator(int answerIDToRemove,
IAnswerRepository answerRepository)
{
...some code
}
public override bool Validates()
{
...some code
}
}
An example test for each are as follows:
[Test]
public void AddAnswerValidatorTests_Validates_ValidInput_ReturnsTrue()
{
var autoMocker = new RhinoAutoMocker<AddAnswerValidator>();
var fakeAnswer = new Answer();
autoMocker.Inject<Answer>(fakeAnswer);
var result = autoMocker.ClassUnderTest.Validates();
Assert.IsTrue(result);
}
[Test]
public void RemoveAnswerValidatorTests_Validates_ValidInput_ReturnsTrue()
{
var autoMocker = new RhinoAutoMocker<RemoveAnswerValidator>();
var fakeAnswerID = 1;
autoMocker.Inject<int>(fakeAnswerID);
var result = autoMocker.ClassUnderTest.Validates();
Assert.IsTrue(result);
}
The first test (for AddAnswerValidator) works fine. The second test (for RemoveAnswerValidator) fails with a StructureMap 202 Error "No default instance defined for plugin family RemoveAnswerValidator" error. I'm working under the assumption that the second test is failing because StructureMap isn't resolving the integer constructor argument.
I've read through this post regarding RhinoAutoMocker Injection for collections and I've been tracing through the source code on GitHub, but I don't understand why the primitive value isn't being injected.
I've even tried substituting some of the overloaded Inject methods available on the container object such as:
autoMocker.Inject<int>(fakeAnswerID);
with
autoMocker.Container.Inject<int>("answerIDToRemove", fakeAnswerID);
but using the name of the constructor argument doesn't produce any different results.
--
In the long run, this isn't a huge problem since I can always just create an instance of the Class Under Test and create my own mocks, it would just be nice to be able to use a consistent methodology across all of my tests.
I know, it's a little bit too late, but i had the same problem and managed to solve it with integer parameter:
var autoMocker = new RhinoAutoMocker<RemoveAnswerValidator>();
automocker.Container.Configure(c=>
c.For<RemoveAnswerValidator>()
.Use<RemoveAnswerValidator>()
.Ctor<int>()
.Is(1));
While I never did find a way to inject a primitive using AutoMocker, I ended up working around the problem by creating a parameter object and injecting that instead.
The parameter object then includes the necessary primitives as a properties, in addition to the other (former) parameters. The example above would be changed to:
public class RemoveAnswerValidator : BaseValidator
{
public RemoveAnswerValidator(RemoveAnswerValidatorParameters parms)
{
...some code
}
public override bool Validates()
{
...some code
}
}
public class RemoveAnswerValidatorParameters
{
public int AnswerID { get; set; }
public IAnswerRepository AnswerRepository { get; set; }
}
(Then in the test class)
[Test]
public void RemoveAnswerValidatorTests_Validates_ValidInput_ReturnsTrue()
{
var autoMocker = new RhinoAutoMocker<RemoveAnswerValidator>();
var fakeAnswerParameters = new FakeAnswerParameters()
{
AnswerID = 1,
AnswerRepository = autoMocker.Get<IAnswerRepository>()
};
autoMocker.Inject<RemoveAnswerValidatorParameters>(fakeAnswer);
var result = autoMocker.ClassUnderTest.Validates();
Assert.IsTrue(result);
}

How to set the test case sequence in xUnit

I have written the xUnit test cases in C#. That test class contains so many methods. I need to run the whole test cases in a sequence. How can I set the test case sequence in xUnit?
In xUnit 2.* this can be achieved using the TestCaseOrderer attribute to designate an ordering strategy, which can be used to reference an attribute that is annotated on each test to denote an order.
For example:
Ordering Strategy
[assembly: CollectionBehavior(DisableTestParallelization = true)]
public class PriorityOrderer : ITestCaseOrderer
{
public IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases) where TTestCase : ITestCase
{
var sortedMethods = new SortedDictionary<int, List<TTestCase>>();
foreach (TTestCase testCase in testCases)
{
int priority = 0;
foreach (IAttributeInfo attr in testCase.TestMethod.Method.GetCustomAttributes((typeof(TestPriorityAttribute).AssemblyQualifiedName)))
priority = attr.GetNamedArgument<int>("Priority");
GetOrCreate(sortedMethods, priority).Add(testCase);
}
foreach (var list in sortedMethods.Keys.Select(priority => sortedMethods[priority]))
{
list.Sort((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.TestMethod.Method.Name, y.TestMethod.Method.Name));
foreach (TTestCase testCase in list)
yield return testCase;
}
}
static TValue GetOrCreate<TKey, TValue>(IDictionary<TKey, TValue> dictionary, TKey key) where TValue : new()
{
TValue result;
if (dictionary.TryGetValue(key, out result)) return result;
result = new TValue();
dictionary[key] = result;
return result;
}
}
Attribute
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class TestPriorityAttribute : Attribute
{
public TestPriorityAttribute(int priority)
{
Priority = priority;
}
public int Priority { get; private set; }
}
Test Cases
[TestCaseOrderer("FullNameOfOrderStrategyHere", "OrderStrategyAssemblyName")]
public class PriorityOrderExamples
{
[Fact, TestPriority(5)]
public void Test3()
{
// called third
}
[Fact, TestPriority(0)]
public void Test2()
{
// called second
}
[Fact, TestPriority(-5)]
public void Test1()
{
// called first
}
}
xUnit 2.* ordering samples here
Testpriority: at the bottom of this page.
[PrioritizedFixture]
public class MyTests
{
[Fact, TestPriority(1)]
public void FirstTest()
{
// Test code here is always run first
}
[Fact, TestPriority(2)]
public void SeccondTest()
{
// Test code here is run second
}
}
BTW, I have the same problem right now. And yes, it is not the clean art.. but QA wanted a manual test.. so an automated test with a specific order already is a big leap for them.. (cough) and yes, it is not really unit testing..
If you really have the need to prioritize your tests (probably not your unit tests) you can use Xunit.Priority.
I have used it for some integration testing and works really well and simple without the overhead of having to write your prioritization classes, for simple case scenarios
For some reason, XUnit.Priority didn't work for me. In my test cases, it wasn't running the tests in the priority order specified.
So I tried XUnitPriorityOrderer, which is similar to use but was working (To quickly test it, save the following code in a text editor as OrderedXUnitTests.linq, then open it with LinqPad 6 and execute it. Alternatively, you can also copy the TestClass to Visual Studio and add XUnit, XUnit.Runner.VisualStudio and XUnitPriorityOrderer):
<Query Kind="Program">
<NuGetReference>XUnitPriorityOrderer</NuGetReference>
<Namespace>Xunit</Namespace>
<Namespace>XUnitPriorityOrderer</Namespace>
</Query>
#load "xunit"
// using XUnitPriorityOrderer
// see: https://github.com/frederic-prusse/XUnitPriorityOrderer
void Main()
{
RunTests(); // Call RunTests() or press Alt+Shift+T to initiate testing.
}
#region private::Tests
[TestCaseOrderer(CasePriorityOrderer.TypeName, CasePriorityOrderer.AssembyName)]
public class TestClass
{
static List<string> Order { get; set; }
public TestClass()
{
Order = Order ?? new List<string>();
}
[Fact, Order(2)]
void Test_Xunit_AnotherTest()
{
Order.Add("Test_Xunit_AnotherTest");
Assert.True(3 + 1 == 4);
}
[Fact, Order(1)]
void Test_Xunit()
{
Order.Add("Test_XUnit");
Assert.True(1 + 1 == 2);
}
[Fact, Order(99)]
void Print_Order()
{
Order.Add("Print_Order");
var strOrder = string.Join(", ", Order.ToArray());
strOrder.Dump("Execution Order");
Assert.True(true);
}
}
#endregion
This will run the tests in given order (Order(1), Order(2) and then Order(99)) and will dump the executed tests finally (test method Print_Order()).
You can't, by design. It's deliberately random in order to prevent anyone getting one of those either by desire or by accident.
The randomness is only for a given Test class, so you may be able to achieve your goals by wrapping items you want to control the order of inside a nested class - but in that case, you'll still end up with random order whenever you have more than two Test Methods in a class.
If you're trying to manage the building up of fixtures or context, the built-in IUseFixture<T> mechanism may be appropriate. See the xUnit Cheat Sheet for examples.
But you really need to tell us more about what you're trying to do or we'll just have to get speculative.

How to mock an interface that extends IEnumerable

I'm using Moq and I have the following interface:
public interface IGameBoard : IEnumerable<PieceType>
{
...
}
public class GameBoardNodeFactory
{
public virtual GameBoardNode Create (int row, int column, IGameBoard gameBoard)
{
...
}
}
Then I have a test like this:
var clonedGameBoardMock = new Mock<IGameBoard> (MockBehavior.Loose);
var gameBoardNodeFactoryMock = new Mock<GameBoardNodeFactory> ();
gameBoardNodeFactoryMock.Setup (x =>
x.Create (
position.Row,
position.Column,
clonedGameBoardMock.Object)).Returns (new GameBoardNode { Row = position.Row, Column = position.Column });
But then gameBoardNodeFactoryMock.Object.Create (position.Row, position.Column, clonedGameBoardMock.Object) throws a NullReferenceException. I tried to create a mock for the IGameBoard such that it doesn't extend IEnumerable<PieceType> interface and then it works.
Any help is appreciated.
You would need to create a Setup for GetEnumerator() if it's being called. Something like:
var mockPieces = new List<PieceType>;
clonedGameBoardMock.Setup(g => g.GetEnumerator()).Returns(mockPieces.GetEnumerator());
Note sure if that's the issue in this case, but worth noting if you ever need to mock IEnumerable<T>.
The answer by #DanBryant was also the key to our solution. However, the enumerator in that case might be accidentally reused. Instead, I suggest using:
clonedGameBoardMock.Setup(g => g.GetEnumerator()).Returns(() => mockPieces.GetEnumerator());
Here's a full repro (new class library using NUnit 2.6.4 and Moq 4.2):
public interface IMyThing<T> : IEnumerable<T>
{
string Name { get; set; }
IMyThing<T> GetSub<U>(U key);
}
public interface IGenericThing
{
string Value { get; set; }
}
public class Pet
{
public string AnimalName { get; set; }
}
public class Unit
{
public IEnumerable<Pet> ConvertInput(IMyThing<IGenericThing> input)
{
return input.GetSub("api-key-123").Select(x => new Pet { AnimalName = x.Value });
}
}
[TestFixture]
public class Class1
{
[Test]
public void Test1()
{
var unit = new Unit();
Mock<IMyThing<IGenericThing>> mock = new Mock<IMyThing<IGenericThing>>();
Mock<IMyThing<IGenericThing>> submock = new Mock<IMyThing<IGenericThing>>();
var things = new List<IGenericThing>(new[] { new Mock<IGenericThing>().Object });
submock.Setup(g => g.GetEnumerator()).Returns(() => things.GetEnumerator());
mock.Setup(x => x.GetSub(It.IsAny<string>())).Returns(submock.Object);
var result = unit.ConvertInput(mock.Object);
Assert.That(result, Is.Not.Null.And.Not.Empty);
Assert.That(result, Is.Not.Null.And.Not.Empty); // This would crash if the enumerator wasn't returned through a Func<>...
}
}
For what it's worth / to make this question pop up to that one lone Googler with the same problem I had: the above is an abstracted version of the Couchbase .NET client's IView<T> interface, which also implements IEnumerable<T>.
A null reference in this situation usually means your setup was never met. Meaning it was never called with the exact values you set it up for. To debug this I would make your match less constraining by using It.IsAny() and so on to make sure the test will match on any call to the mocked function. In most cases this is good enough. Any reason why your are trying to match on specific values?
Okay if anyone is interested, I updated Moq to version 4 and now everything works as expected.

How to design a method that contains lots of IF's or Switch statements

How can i make the design of this class more dynamic so i can add new extensions and types as required.
public class Processor
{
public Processor(string fileName)
{
string extension = Path.GetExtension(fileName);
if(extension == "jpg" || extension == "gif" || extension == "png")
{
//Process Image file type
}
else if(extension == "xls" || extension == "xlsx")
{
//Process spreadsheet type
}
else if(extension == "doc" || extension == "docx")
{
//Process document file type
}
//and so forth ...
}
}
We may need to process .tiff files in future or we may need to process video files, which means a new if branch
else if(extension == "avi" || extension == "mp4")
{
//Process video file type
}
As you can see this can get very long.
The valid files types and groups are stored in a DB...
Can anyone recommend any patterns or clever ideas to solve this problem?
Cheers
Use a dictionary.
Dictionary<string, IFileHandler> fileHandlers = new Dictionary<string, IFileHandler>
{
{ "jpg", imageHander },
{ "gif", imageHander },
{ "xls", spreadsheetHander },
// ...
};
Then use it as follows:
public void Process(string fileName)
{
string extension = Path.GetExtension(fileName);
// TODO: What should happen if the filetype is unknown?
fileHandlers[extension].Process(fileName);
}
The valid files types and groups are stored in a DB...
Then you probably want to query the database to get the correct group from the extension and use the group as the dictionary key rather than the extension.
I would recommend coming up with an interface for your various file processors to implement:
public interface IFileProcessor
{
void Process(string fileName);
IEnumerable<string> FileExtensions {get;}
}
Then use a factory to get the appropriate processor for a particular file name. Like the other answerers, I'm recommending you use a Dictionary to keep a mapping of the processors and their file names. This example uses reflection to register every file processor that it finds in the loaded assemblies. This fits your extensibility requirement well, because all you need is for some loaded assembly to have a class that implements this interface, and it will automatically be registered with the factory. However, you can come up with whatever system you want for registering these types:
public class ProcessorFactory
{
static IDictionary<string, IFileProcessor> ProcessorsByExtension =
new Dictionary<string, IFileProcessor>();
static ProcessorFactory()
{
var processorTypes =
from a in AppDomain.CurrentDomain.GetAssemblies()
from t in a.GetTypes()
where typeof(IFileProcessor).IsAssignableFrom(t)
select t;
foreach(var t in processorTypes)
{
// Preferably use your DI framework to generate this.
var processor = (IFileProcessor)Activator.CreateInstance(t);
foreach(var ext in processor.FileExtensions)
{
if(ProcessorsByExtension.ContainsKey(ext))
{
throw new InvalidOperationException(
"Multiple processors are registered to extension " + ext);
}
ProcessorsByExtension[ext] = processor;
}
}
}
public IFileProcessor GetProcessorForFile(string fileName)
{
string extension = Path.GetExtension(fileName);
return ProcessorsByExtension[extension];
}
}
A typical implementation of the file processor interface might look like this:
public class ImageFileProcessor : IFileProcessor
{
public IEnumerable<string> FileExtensions
{
get {return new[]{"jpg", "gif", "png"};}
}
public void Process(string fileName)
{
// Process Image file type
}
}
This approach keeps your code modular and extensible, while providing excellent performance even if you have an enormous list of file processors.
This structure is generally a bad thing if it's expected to be extended, for the reasons you state (primarily having to to back in and add code branches).
I would implement this as a Dictionary, keyed to the file extension strings, and containing method delegates as values. That would, if properly designed, allow you to add new extensions without affecting old code, since the new code would be in a completely different method that could even live outside the class that has this logic. Then, simply look up the proper delegate to call by the file extension.
Basic example:
//Put this in your class somewhere
public readonly Dictionary<string, Action<FileInfo>> fileHandlers = new Dictionary<string, Action<FileInfo>>();
...
//depending on the dictionary's visibility, you can add these from pretty much anywhere
fileHandlers.Add("xls", ProcessExcelFile);
fileHandlers.Add("xlsx", ProcessExcelFile);
fileHandlers.Add("jpg", ProcessImageFile);
...
//Then all you have to do to invoke the logic is...
fileHandlers[extension](fileInfo);
One way might be to build a table (list), where each item contains the extension along with information about what to do with names with that extension.
You can then write a simple loop, that compares against each extension in the table.
If what you do with each extension is complex and can't be defined in a simple table, then you could instead include a delegate. When the loop finds the matching extension, it could call the corresponding delegate to process the name.
There is a rule for this refactoring--
Replace condition with Polymorphism/strategy.
The obvious problem with if/switch is very likely to have errors in such code, and it is difficult to maintain or enhance.
Considering the same and the "Open-Closed Principal" ( -- Classes should be open for extension but closed for modifications.).
I would suggest for the code below.
public class Processor
{
private Dictionary<string,FileParserBase> _fileExtension2FileParser;
public Processor() {
_fileExtension2FileParser = new Dictionary<string, FileParserBase>();
AddParser(new DocExtensionWordParser());
AddParser(new DocXExtensionWordParser());
//..more,more
}
private void AddParser(FileParserBase fileParserBase) {
_fileExtension2FileParser.Add(fileParserBase.Extension, fileParserBase);
}
public void Process(string fileName)
{
string extension = Path.GetExtension(fileName);
FileParserBase fileParser;
if (_fileExtension2FileParser.TryGetValue(extension, out fileParser)) {
fileParser.Process(fileName);
}
}
}
public interface FileParserBase
{
string Extension { get; }
void Process(string filePath);
}
public abstract class WordParserBase : FileParserBase
{
private string _extension;
public WordParserBase(string extension)
{
_extension = extension;
}
public override void Process(string filePath)
{
//Do the processing for WORD Document
}
public override string Extension
{
get { return _extension; }
}
}
public class DocExtensionWordParser : WordParserBase
{
public DocExtensionWordParser():base("doc"){}
}
public class DocXExtensionWordParser : WordParserBase
{
public DocXExtensionWordParser() : base("docx") { }
}
for this particular case I'd build something like
List<string> excelExtensions = new List<string>(){ "xls", "xlsx" };
List<string> wordExtensions = new List<string>(){ "doc", "docx" };
if(excelExtensions.Contains(extension))
{
}
if(wordExtensions.Contains(extension))
{
}
etc?

Categories