First, several concepts 1. What is external dependency
External dependency refers to the object that the code interacts with in the system, and it cannot be controlled by human beings.
The most common examples are file systems, threads, memory, and time, and we use pile objects to handle external dependency problems.
2. What is a pile object
The pile object is a substitute for the existing dependence in the system, which can be controlled artificially.
By using a pile object, you can test the code directly without involving dependencies.
3. What is refactoring
Refactoring refers to a behavior that changes code design without affecting existing functionality
4. What is a seam
Seams are places in your code where you can insert different functions, such as pile object classes.
II. release of Reliance
Abstract an interface
namespace logan.interface{ publicInterface iextensionmanager { bool IsValid (string fileName);} }
Specific classes that implement interfaces
namespacelogan.implement{ Public classFileextensionmanager:iextensionmanager { Public BOOLIsValid (stringfileName) { if(string. IsNullOrEmpty (FileName)) {Throw NewArgumentException ("No filename provided!"); } if(!filename.endswith (". SLF")) { return false; } Else { return true; } } }}
Write a Pile object class that implements the interface
Regardless of the extension class of the file, this pile object class always returns True
Public class stubextensionmanager:iextensionmanager{ publicbool IsValid (string FileName) { returntrue; }}
Writing the method under test
An existing interface and two classes to implement the interface, but the test class or directly invoke the "true object";
At this point we need to introduce seams in the code so that we can use the pile object
The realization of injecting the pile object into the tested class;
Receives an interface at the constructor level;
namespace logan{ publicclass loganalyzer { publicBOOL Isvalidlogfilename (string fileName) { =new Fileextensionmanager ( ); return Mgr. IsValid (fileName); }}}
Iii. injecting the pile object into the class being tested--the constructor function
1. Rewrite LogAnalyzer.cs
namespacelogan{ Public classLoganalyzer {PrivateIextensionmanager Manager; /// <summary> ///Create a new object in production code/// </summary> PublicLoganalyzer () {Manager=NewFileextensionmanager (); } /// <summary> ///define the constructors that are available for test calls/// </summary> /// <param name= "Mgr" ></param> PublicLoganalyzer (Iextensionmanager Mgr) {Manager=Mgr; } Public BOOLIsvalidlogfilename (stringfileName) { returnManager. IsValid (FileName); } }}
2. Writing the Pile object
Public class Stubextensionmanager:iextensionmanager { publicbool shouldextensionbevalid; publicbool IsValid (string fileName) { return shouldextensionbevalid;} }
3. Writing test methods
[Testfixture] Public classloganalyzertest{[Test] Public voidisvalidfilename_validfilelowercased_returntrue () {Stubextensionmanager Myfakemanager=NewStubextensionmanager (); Myfakemanager.shouldextensionbevalid=true; Loganalyzer Analyzer=NewLoganalyzer (Myfakemanager); BOOLresult = Analyzer. Isvalidlogfilename ("HAHA.SLF"); Assert.istrue (Result,"filename shoud be valid!"); }}
4. Problems existing in the method of constructor injection
If the code under test needs more than one pile object to function properly, you need to add more constructors, which can cause great distress and even reduce the readability and maintainability of the code.
5. When to use the constructor injection method
Using constructors, you can tell the API consumer well: "These parameters are required, and all parameters must be passed in when you create the object"
If you want these dependencies to become optional, you can use attribute injection
Iv. injecting the pile object into the test class-attribute injection
1. Rewrite LogAnalyzer.cs
namespacelogan{ Public classLoganalyzer {PrivateIextensionmanager Manager; /// <summary> ///Create a new object in production code/// </summary> PublicLoganalyzer () {Manager=NewFileextensionmanager (); } /// <summary> ///allow dependency through property setting/// </summary> /// <param name= "Mgr" ></param> PublicIextensionmanager Extensionmanager {Get{returnManager;} Set{manager =value;} } Public BOOLIsvalidlogfilename (stringfileName) { returnManager. IsValid (FileName); } }}
2. Writing the Pile object class
Public class stubextensionmanager:iextensionmanager{ publicbool shouldextensionbevalid; publicbool IsValid (string fileName) { return shouldextensionbevalid;} }
3. Writing test methods
[Testfixture] Public classloganalyzertest{[Test] Public voidisvalidfilename_validfilelowercased_returntrue () {Stubextensionmanager Myfakemanager=NewStubextensionmanager (); Myfakemanager.shouldextensionbevalid=true; Loganalyzer Analyzer=NewLoganalyzer (); Analyzer. Extensionmanager=Myfakemanager; BOOLresult = Analyzer. Isvalidlogfilename ("HAHA.SLF"); Assert.istrue (Result,"filename shoud be valid!"); }}
V. Injecting the pile object into the test class-factory method
1. Writing LogAnalyzer.cs
namespacelogan{ Public classLoganalyzer {PrivateIextensionmanager Manager; /// <summary> ///using the factory in production code/// </summary> /// <param name= "Mgr" ></param> PublicLoganalyzer () {Manager=extensionmanagerfactory.create (); } Public BOOLIsvalidlogfilename (stringfileName) { returnManager. IsValid (FileName); } }}
2. Writing test methods
[Testfixture] Public classloganalyzertest{[Test] Public voidisvalidfilename_validfilelowercased_returntrue () {Stubextensionmanager Myfakemanager=NewStubextensionmanager (); Myfakemanager.shouldextensionbevalid=true; //assign the pile object to the factory classExtensionmanagerfactory.setmanager (Myfakemanager); Loganalyzer Analyzer=NewLoganalyzer (); BOOLresult = Analyzer. Isvalidlogfilename ("HAHA.SLF"); Assert.istrue (Result,"filename shoud be valid!"); }}
Reading notes-unit test Art (iii)-using pile objects to lift dependencies