K čemu reflexe? Názvy atributů mohu měnit dle libosti, aniž by to mělo na unit testy i na zbytek aplikace sebemenší vliv, protože je prostě nevidí. Testy testují pouze rozhraní třídy, atributy se v něm nevyskytují. Mocky nastrkám do konstruktoru a jako parametry metod. Jinde je nepotřebuji.
Jak jako Mocky nastrkáš do konstruktoru. Co když se ve třídě vytváří objekty jiné třídy a ty je budeš musel v unit testu namockovat, jinak se ti zavolají.
To se dělá tak, že se ty objekty jiné třídy injektují do konstruktoru nebo do parametru metody. Zjednoduší se tím design třídy a zlepší robustnost aplikace.
To jako uděláš jeden velký zbytečný konstruktor, který bude obsahovat všechny vytvářené instance ve třídě? Wtf.
Ne. Do konstruktoru injektuji již vytvořené a nakonfigurované vnější objekty. Konstruktor si je pouze uloží mezi atributy. Tím se rapidně zlepšuje znovupoužitelnost třídy a polymorfismus zde získává ty správné možnosti.
WTF! Tak tady máš frajere a ukaž se, jak otesteješ (a nebo upravíš) třídu Program a metodu Run().
public class Program
{
OptionSet o;
// Console params
public String FeedFilePath { get; set; }
public String TargetLocation { get; set; }
public int? TestFileCount { get; set; }
public bool Help { get; set; } = false;
public int? BatchSize { get; set; }
public IProcessor TradeProcessor { get; set; }
public Tester Tester { get; set; }
public Program(params string[] args)
{
LoggerSetting.init();
o = new OptionSet()
{
{ "f|filePath=", "the input {FILE} path of Trade datafeed. Defaul is current dir + '/TradesList.xml'.", v => FeedFilePath = v },
{ "t|targetPath=", "the {DESTINATION} path. Default is current dir.", v => TargetLocation = v },
{ "g|testFile=", "generate TradesList.xml of {COUNT} elements in current dir.", v => TestFileCount = Int32.Parse(v) },
{ "b|batchSize=", "Batch {SIZE}. Default is 1000.", v => BatchSize = Int32.Parse(v) },
{ "h|help", "Show this help.", v => Help = v != null}
};
List<string> extra;
try
{
extra = o.Parse(args);
}
catch (OptionException e)
{
Console.Write("datafeeds: ");
Console.WriteLine(e.Message);
Console.WriteLine("Try `--help' for more information.");
return;
}
if (Help)
{
printHelp();
return;
}
if (FeedFilePath == null)
{
FeedFilePath = AppDomain.CurrentDomain.BaseDirectory + "/TradesList.xml";
}
if (TargetLocation == null)
{
TargetLocation = AppDomain.CurrentDomain.BaseDirectory;
}
if (BatchSize == null)
{
BatchSize = 1000;
}
TradeProcessor = new TradeProcessor(this.FeedFilePath, this.TargetLocation, BatchSize.Value);
Tester = new CTSTestApplication.Tester();
}
public void run()
{
if(TestFileCount.HasValue)
{
Tester.CreateTestFile(AppDomain.CurrentDomain.BaseDirectory, TestFileCount.Value);
}
if (TradeProcessor != null)
{
TradeProcessor.Process();
}
}
private void printHelp()
{
Console.WriteLine("help");
o.WriteOptionDescriptions(Console.Out);
}
public static void Main(string[] args)
{
Program program = new Program(args);
program.run();
}
}
[TestClass()]
public class ProgramTests
{
TradeProcessorMock tradeProcessorMock = new TradeProcessorMock();
[TestMethod()]
public void ProgramTest()
{
string path = AppDomain.CurrentDomain.BaseDirectory;
string s = "-filePath=" + path;
string s2 = "-targetPath=" + path;
string s3 = "-testFile=150";
string s4 = "-batchSize=999";
Program p = new Program(s, s2, s3, s4);
Assert.AreEqual(p.FeedFilePath, path);
Assert.AreEqual(p.TargetLocation, path);
Assert.AreEqual(p.TestFileCount, 150);
Assert.AreEqual(p.BatchSize, 999);
p.TradeProcessor = tradeProcessorMock;
p.TestFileCount = null; // Can't mock Tester
p.run();
Assert.AreEqual(tradeProcessorMock.ProcessRunCount, 1);
}