Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
using System;
using System.Text.RegularExpressions;
namespace BasicApp.App.Utilities
{
public class UtilitiesProfession
{
public enum Gender
{
Male = 1,
Female = 2
}
public string GetCompleteProfession(string professionName, Gender g)
{
string StrPronoun = string.Empty;
if (g == Gender.Male)
StrPronoun = "he";
else
StrPronoun = "she";
if (Regex.IsMatch(professionName, "^[aeiou]"))
return StrPronoun + " is an " + professionName;
else
return StrPronoun + " is a " + professionName;
}
}
public class UtilitiesDate
{
private IReader _Reader;
private IWriter _Writer;
private string _MonthsAsString;
private void SetMonthsAsString()
{
_MonthsAsString = "";
_MonthsAsString += "January\r\nFebruary\r\nMarch\r\n";
_MonthsAsString += "April\r\nMay\r\nJune\r\n";
_MonthsAsString += "July\r\nAugust\r\nSeptember\r\n";
_MonthsAsString += "October\r\nNovember\r\nDecember";
}
// Default Constructor
public UtilitiesDate(){
SetMonthsAsString();
}
// Get time difference in weeks or days from DateTime t1 to DateTime t2.
public decimal GetWeeks(DateTime dtFrom, DateTime dtTo)
{
int Days = ((TimeSpan)(dtTo - dtFrom)).Days;
decimal Weeks = Math.Ceiling((decimal)Days / 7);
return Weeks;
}
public decimal GetDays(DateTime dtFrom, DateTime dtTo)
{
int Days = ((TimeSpan)(dtTo - dtFrom)).Days;
return Days;
}
/*********************/
/* STUBS: CASE STUDY */
/*********************/
/* There are multiple ways by which we can inject stubs into our code, the challenge is choosing
* the correct location for the "seam", which is defined as a place where we can choose either
* a fake or the real to use in execution.
*
* The following stub injection tricks, explored in The Art of Unit Testing, are shown here and will
* be the center of this study.
*
* Injection by Parameter
* Injection by Constructor
* Injection by Property Setters
* Injection using Factory
* Injection using Local Factory (Extract & Override)
*/
/* The following method reads a simple project text file that contains all the months listed in order.
* It will return an array of strings where each element is the name of a month. We will use it as a test
* case.
*/
public string[] GetMonths()
{
IReader Reader = new FileReader();
string Months = Reader.ValidateAndRead("file.txt");
return Regex.Split(Months, "\r\n");
}
/* INJECTION BY PARAMETER
*
* Simple & trival way to inject stub, but you might not want to expose dependency on
* reader in public method. Not much else to say.
*/
public string[] GetMonthsStubByParamInject(IReader reader)
{
string Months = reader.ValidateAndRead("file.txt");
return Regex.Split(Months, "\r\n");
}
/* INJECTION BY CONSTRUCTOR
*
* Simple but won't scale well as more stubs will require more properties which means more constructors
* and method arguements in constructors. Besides being messy it also leads to potential
* time wasting. Imagine that you have 50 tests against the constructor and you find a new dependency
* that requires a stub. You'll need to add a parameter to the constructor in all those tests.
*
* A few potential solutions to constructor injection may include ...
*
* To handle neatness: a special wrapper function that locally defines values needed to initialize a class.
* And only one paramter is needed to define the class type.
*
* To handle dependancy issues: Inversion of Control (IoC). A construct where you "say" what you want and it goes "back"
* to initialize the needed dependencies to then give you what you want. More details can be found here.
* http://www.hanselman.com/blog/ListOf-NETDependencyInjectionContainersIOC.aspx.
*/
public UtilitiesDate(IReader reader)
{
_Reader = reader;
SetMonthsAsString();
}
public string[] GetMonthsStubByConstructorInject() {
string Months = _Reader.ValidateAndRead("file.txt");
return Regex.Split(Months, "\r\n");
}
/* INJECTION BY PROPERTY SETTERS
*
* Our method is no different than before but now we can set our reader outside of the constructor.
* We make it explicity clear that the reader is optional and keep our constructors clean and simple.
*/
public IReader Reader
{
get { return _Reader; }
set { _Reader = value; }
}
public string[] GetMonthsStubByPropertyInject() {
string Months = _Reader.ValidateAndRead("file.txt");
return Regex.Split(Months, "\r\n");
}
/* INJECTION USING FACTORY
*
* It be cool if we could get our stub locally, right before the actual function call we are testing, instead of
* relying on global fields. To do this we need a factory. The factory class will now be responsible for generating
* our object. It will contain the seam in our code (the point at which we can generate a fake or a real). Since it's
* static we can define what ReaderFactory will output on Create.
*/
public string[] GetMonthsStubUsingFactory() {
_Reader = ReaderFactory.Create();
string Months = _Reader.ValidateAndRead("file.txt");
return Regex.Split(Months, "\r\n");
}
/* One of the important concepts to understand within unit tests are the layers of indirection. Whenever you test you are always
* testing a certain layer of your program. Whether it be the main function or a subroutine on the extremity of the application you
* are always somewhere within the call stack. Typically the deeper you go the more control you have over what you are testing but on
* the other hand these low level tests are obscure and difficult to understand.
*
* Choosing where you want to place the seams is one of the major challenges of developing good unit tests.
*
* Thus far we have looked at two important locations where seams (specifically fakes) can be introduced.
*
* Firstly we looked at choosing to fake the members in the tested class. One of the key issues here is that we are
* changing the semantics of the code within the class we are testing, more importantly how it must be used.
*
* Secondly we looked at choosing to fake the members inside of the factory class and with that we managed to avoided changing the
* code inside the class which we are testing.
*/
/* INJECTION USING LOCAL FACTORY (EXTRACT AND OVERRIDE)
*
* Is an easy way to avoid depedancy issues when creating stubs by using a derivation of the class that is under test. We can
* call the derived version the "testable" class. It's incredible clean and simple, it should be your default technique for stub
* creation. The only time when you might want to use the other techniques is if the code base is already supportive of the other
* techniques. For example there's an interface available for faking (you don't need to make one) or a location in the code where a
* seam can be injected.
*/
protected virtual IReader GetReader()
{
return new FileReader();
}
public string[] GetMonthsStubUsingLocalFactory()
{
_Reader = GetReader();
string Months = _Reader.ValidateAndRead("file.txt");
return Regex.Split(Months, "\r\n");
}
/* One problem with Testable Object Oriented Design (TOOD) is that it requires a lot of exposure of a class's internals. Arguably this counters
* the encapsalation principle. If you feel making extra public getters, setters and constructors for testing isn't the way you want to go
* you can always make these methods internal (over public) and use conditional attributes to make sure they are only used during
* debug builds. Having these in your code will make it less readable, primary reason I didn't include them here, but it's an option
* if security and testing are both essential.
*/
/*********************/
/* MOCKS: CASE STUDY */
/*********************/
/* The difference between mocks and stubs can seem subtle at times. A mock is an object
* that we can use for assertion. When object A interacts with object B that should lead to a
* certain state change in B, if it doesn't the interaction failed. In this scenario we can make a
* mock of B. A more technical difference between mocks and stubs is that a correctly written stub can never
* make a test fail, but a mock can.
*
* The following Mocking techniques are explored.
*
* Use of Manually Written Mocks (Reference test code for documentation)
* Use of Strict Mocks (Reference test code for documentation)
* Use of Non-Strict Mocks (Reference test code for documentation)
* Use of Stubs (Reference test code for documentation)
* Constraints (Reference test code for documentation)
* Delegates (Reference test code for documentation)
* AAA (Arrange, Assert, Act) (Reference test code for documentation)
*
*/
/* The following method will be used as a test case, like the previously defined getMonths, except this time we
* will be using it to test mocks. The method will simply write all the months in year to a file in the designated
* path. Each month will be written on a new line. If we successfully manage to write we return true else we return false.
* An important note here is that we aren't mocking the file system but rather the object that makes the low level
* call to write to the file system.
*/
protected virtual IWriter GetWriter()
{
return new FileWriter();
}
public bool SetMonths(string path)
{
_Writer = GetWriter();
if (_Writer.ValidateAndWrite(path, _MonthsAsString))
return true;
else
return false;
}
public bool SetMonthsUsingWriterInput(string path)
{
_Writer = GetWriter();
if (_Writer.ValidateAndWrite(new WriterInput(path, _MonthsAsString)))
return true;
else
return false;
}
}
/*MANUALLY WRITTEN STUB*/
public interface IReader
{
string ValidateAndRead(string path);
}
public class FileReader : IReader
{
public string ValidateAndRead(string path)
{
return System.IO.File.ReadAllText(path);
}
}
public class StubReader : IReader
{
public string ValidateAndRead(string path)
{
string S = "";
S += "January\r\nFebruary\r\nMarch\r\n";
S += "April\r\nMay\r\nJune\r\n";
S += "July\r\nAugust\r\nSeptember\r\n";
S += "October\r\nNovember\r\nDecember";
return S;
}
}
/*MANUALLY WRITTEN MOCK*/
public interface IWriter
{
bool ValidateAndWrite(string path, string content);
bool ValidateAndWrite(WriterInput input);
}
public class FileWriter : IWriter
{
public bool ValidateAndWrite(string path, string content)
{
try {
System.IO.File.WriteAllText(path, content);
return true;
} catch {
return false;
}
}
public bool ValidateAndWrite(WriterInput input) {
// parse the WriterInput object and do the same as before
return true;
}
}
public class MockWriter : IWriter
{
public string _Path;
public string _Content;
public bool ValidateAndWrite(string path, string content)
{
_Path = path;
_Content = content;
return true;
}
public bool ValidateAndWrite(WriterInput input){
// parse the WriterInput object and do the same as before
return true;
}
}
/*HELPERS*/
public class WriterInput
{
private string _Path;
private string _Content;
public string Path
{
get {return _Path; }
set {_Path = value;}
}
public string Content
{
get { return _Content; }
set { _Content = value; }
}
public WriterInput(string path, string content) {
_Path = path;
_Content = content;
}
}
public class ReaderFactory
{
private static IReader _Reader = null;
public static IReader Create() {
if (_Reader != null)
return _Reader;
else
return new FileReader();
}
public static void SetReader(IReader reader){
_Reader = reader;
}
}
public class TestableUtilitiesDate : UtilitiesDate {
public IReader _Reader;
public IWriter _Writer;
protected override IReader GetReader() {
return _Reader;
}
protected override IWriter GetWriter()
{
return _Writer;
}
}
}