400 likes | 637 Views
Test-Driven Development and Unit Testing. Agenda. Writing a Unit Test Asserts Test initialization and destruction Generating Tests. Why unit tests?. To the extent possible, tests should run at all levels unit (smallest granularity) functional (public component contract)
E N D
Agenda • Writing a Unit Test • Asserts • Test initialization and destruction • Generating Tests
Why unit tests? • To the extent possible, tests should run at all levels • unit (smallest granularity) • functional (public component contract) • integration (components in contact with each other) • Time spend testing "in the small" will save time later • Agile practitioners write tests first • if not first, tests should at least be in small grain, in conjunction with the code being tested
Planning Unit Tests public bool AgeCheck (DateTime dayOfBirth) { //Method returns true if the person is over age 18, otherwise false …… }
Some Tests for AgeCheck • Check a date from exactly 18 years ago to the day. • Check a date from further back in time than 18 years. • Check a date from fewer than 18 years ago. • Check for a null value. • Check for the results based on the very minimum date that can be entered. • Check for the results based on the very maximum date that can be entered. • Check for an invalid date format being passed to the method. • Check two year dates (such as 05/05/03) to determine what century the two-year format is being treated as. • Check the time (if that matters) of the date.
Unit Test Areas • Boundary Values – min, max, etc. • Equality – eg. 18 • Format – never trust data passed to your application. • Culture – 11/26/07 vs. 26/11/07 • Exception Path
Test-Driven Development • Write the Unit Test • Write enough code for the test to succeed • Refactor the code to make it more efficient
Why automate tests? • Software without test cases must be assumed defective • Manual test cases are unmaintainable • no guarantee that all tests will be run regularly, or at all • even if tests are run, process is tedious and error-prone • Automated testing becomes part of the build process • easy to verify that changes do not break existing functionality • encourages "build for now" • encourages refactoring
Test Guidelines • Keep application layered • Keep business logic separate from UI • Can then test business logic more easily
Visual Studio Team Test • Integrated into Visual Studio • Can generate tests from code • Setup/Teardown for all tests • Setup/teardown for individual tests • Methods are annotated to make them a test case
Test Driven Development in Visual Studio • Create a Project • Create a related Test Project • Write test case • Write 'real' code • Wash, rinse, repeat
StarshipTest.cs using Microsoft.VisualStudio.TestTools.UnitTesting; namespaceDMTest { [TestClass] publicclass StarshipTest { [TestMethod] publicvoid TestWarpFactorLessThanMaxWarp() { } } } Writing a Test Class • Test class contains test cases • Class and test identified by attributes
StarshipTest.cs [TestMethod] publicvoid TestWarpFactorLessThanMaxWarp() { Starship ship = new Starship(); ship.WarpFactor = 5; Assert.AreEqual<Double>(ship.WarpFactor, 5); } Using Asserts • Write tests by asserting truths • Use Assert class static methods • Several methods are available • Support different types through generics
Assert class • Static methods • AreEqual • AreNotEqual • AreNotSame • AreSame • Fail • Inconclusive • IsFalse • IsInstanceOfType • IsNotInstanceOfType • IsNotNull • IsNull • IsTrue
CollectionAssert class • Equivalent class for collections • AllItemsAreInstancesOfType • AllItemsAreNotNull • AllItemsAreUnique • AreEqual • AreEquivalent (same elements) • AreNotEqual • AreNotEquivalent • Contains • DoesNotContain • IsNotSubsetOf • IsSubsetOf
Starship.cs namespace Space { publicclass Starship { privatedouble _warpFactor; publicdouble WarpFactor { get { return _warpFactor; } set { _warpFactor = value; } } } } Write the code • Write enough code so that the test passes
Running tests – Visual Studio • Visual Studio Test Manager • Controls which tests to run ... • ... and when to run them • Allows for debugging of tests
Managing Tests • Test manger • Lets you specify the tests to run • Add lists of tests • Specify tests to run • Specify tests to debug • Data held in .vsmdi (test metadata) file
Configuring Test Runs • Specify data about test runs • Where to run; Enable code coverage; etc… • Can have multiple configurations per project • Set active configuration in Test menu
TestResults Directory • Tests are not run from solution or project directory • Special TestResults directory is created • Contains subdirectory per test run • Subdirectory name is based on the timestamp of current run • This contains a directory called 'Out' • Tests are run from 'Out' directory
StarshipTest.cs [TestMethod] [DeploymentItem("SomeFile")] publicvoid TestSomeThing() { } DeploymentItem Attribute • Testing code and tested code may need access to files • Config files etc • Use [DeploymentItem] to copy files to 'out' directory • Can specify a single file or a directory • Value can be relative or absolute
Test Results • Appear in pane • Detailed results also available
StarshipTest.cs StarshipTest.cs [TestMethod] public void TestWarpFactorLessThanMaxWarp(){ try{ Starship ship = new Starship(); ship.WarpFactor = Starship.MAX_WARP / 2; Assert.AreEqual<Double>(ship.WarpFactor, (Starship.MAX_WARP / 2)); } catch (Exception){ Assert.Fail("unexpected exception"); } } [TestMethod] [ExpectedException(typeof(ArgumentException))] public void TestWarpFactorGreaterThanMaxWarp(){ Starship ship = new Starship(); ship.WarpFactor = Starship.MAX_WARP + 2; } Testing Exceptions • Some code will throw exceptions • Can test for expected and unexpected exceptions
Starship.cs StarshipTest.cs [TestMethod] [ExpectedException(typeof(ArgumentException))] public void TestWarpFactorGreaterThanMaxWarp() { Starship ship = new Starship(); ship.WarpFactor = 11; } public double WarpFactor{ get { return _warpFactor; } set {if (value > MAX_WARP) thrownew ArgumentException("Invalid warp"); _warpFactor = value; } } Write Next Test • Write test • write code to ensure test passes
StarshipTest.cs StarshipTest.cs [TestMethod] [ExpectedException(typeof(ArgumentException))] public void TestWarpFactorNegative(){ Starship ship = new Starship(); ship.WarpFactor = -1; } [TestMethod] [ExpectedException(typeof(ArgumentException))] publicvoid TestWarpFactorEqualsMaxWarp(){ Starship ship = new Starship(); ship.WarpFactor = Starship.MAX_WARP; } Test Edge Conditions • Edge conditions are where things notoriously go wrong • negative values • values at maximum or minimum
StarshipTest.cs [TestMethod] public void TestFirePhotonTorpedoe() { Starship ship = new Starship(); Assert.Inconclusive("Code not specified"); } Inconclusive tests • Sometimes want a test place holder • Can generate an 'inconclusive' assert • Used by VSTS when generating test code
StarshipTest.cs [TestMethod] [Ignore] public void TestFirePhotonTorpedoe() { Starship ship = new Starship(); Assert.Inconclusive("Code not specified"); } Ignoring Tests • Used when you do not want the test to run
Initializing Tests and Classes • Common code can be refactored • Per class initialization • Per test initialization • Use attributes • [ClassInitialize] • [ClassCleanup] • [TestInitialize] • [TestCleanup]
StarshipTest.cs StarshipTest.cs [TestMethod] public void TestWarpFactorGreaterEqualsMaxWarp(){ ship.WarpFactor = Starship.MAX_WARP; } private Starship ship = null; [TestInitialize] public void TestSetup(){ // called for each test ship = new Starship(); } Initialization example • ClassInitialize used to perform one off initialization • Used if per test initialization is too expensive • TestInitialization • Called per test • Better practice than ClassInitialize Starship created here, and used here
Testing existing code • VSTS can generate tests • Not test driven development • However, can help with existing code
StarshipTest.cs [TestClass()] public class StarshipTest { [ClassInitialize()] public void InitializeClass() {} [TestInitialize()] public void SetUp() {} [TestMethod()] public void CurrentWarpFactorTest() {} [TestCleanup()] public void InitializeClass() {} [ClassCleanup()] public void InitializeClass() {} } What does Generated Code Look Like • Attributes used to specify class/method behaviour
Starship.cs public class Starship { private void TorpedoesLeft(int numberToFire) { MaxTorpedoes -= numberToFire; } } Testing Private Code • Suppose you have a private method • Can’t call this directly from a test case • Need to use reflection to call method • VSTS will generate a stub to class manage this code • Stub has an accessor
StarshipTest.cs [TestMethod()] public void TorpedoesLeftTest() { Starship target = new Starship(); DMTest.Space_StarshipAccessor accessor = new DMTest.Space_StarshipAccessor(target); int numberToFire = 0; // TODO: Initialize to an appropriate value accessor.TorpedoesLeft(numberToFire); ... } Calling the Private Method • Test code calls the accessor method
Starship.cs import Microsoft.VisualStudio.TestTools.UnitTesting; internal class BaseAccessor { protected PrivateObject m_privateObject; protected BaseAccessor(object target, PrivateType type) { m_privateObject = new PrivateObject(target, type); } ... } internal class Space_StarshipAccessor : BaseAccessor { protected staticPrivateType m_privateType = new PrivateType(typeof(global::Space.Starship)); internal Space_StarshipAccessor(global::Space.Starship target) : base(target, m_privateType) { } internal void TorpedoesLeft(int numberToFire) { object[] args = new object[] {numberToFire}; m_privateObject.Invoke("TorpedoesLeft", new System.Type[] {typeof(int)}, args); } } Generated Code for Private Test Case • Access class uses reflection
What is Code Coverage • Information about the code paths executed • Can be supplied automatically during unit tests • Can instrument code to provide data during other tests
Automatic code coverage • Create or amend a run configuration
Displaying Code Coverage • Run tests • Display code coverage
code touched code not touched code partially touched Code Coverage Results • Code shown in different colours • May want to change the colours from the default pastel shades
Using MSTest mstest /testcontainer:StartshipTest\bin\Debug\StartshipTest.dll mstest /testmetadata:TDD.vsmdi mstest /runconfig:localtestrun.testrunconfig /testmetadata:tdd.vsmdi Running test – command line • VSTS comes with mstest command line utility • useful when using MSBuild to manage build process
Summary • VSTS provides a good unit testing framework • Can define test class, test cases, etc. • Attribute driven • Various ways of asserting truth of test • Various ways of initializing tests • Can generate tests from code • Can also test private methods