diff --git a/aspnet/testing/unit-testing.rst b/aspnet/testing/unit-testing.rst index bdcf1e5ef5..f76936c7e2 100644 --- a/aspnet/testing/unit-testing.rst +++ b/aspnet/testing/unit-testing.rst @@ -2,28 +2,124 @@ Unit Testing ============ By `Steve Smith`_ -This content is in progress. +ASP.NET 5 has been designed with testability in mind, so that creating unit tests for your applications is easier than ever before. This article briefly introduces unit tests (and how they differ from other kinds of tests) and demonstrates how to add a test project to your solution and then run unit tests using either the command line or Visual Studio. -In this article: - - `Introduction to Unit Testing`_ - - `Creating Test Projects`_ - - `Running Tests`_ +.. contents:: In this article: + :local: + :depth: 1 +`Download sample from GitHub `_. -`Download sample from GitHub `_. - -Introduction to Unit Testing +Getting Started with Testing ---------------------------- -About unit testing + +Having a suite of automated tests is one of the best ways to ensure a software application does what its authors intended it to do. There are many different kinds of tests for software applications, including :doc:`integration tests `, web tests, load tests, and many others. At the lowest level are unit tests, which test individual software components or methods. Unit tests should only test code within the developer's control, and should not test infrastructure concerns, like databases, file systems, or network resources. Unit tests may be written using `Test Driven Development (TDD) `_, or they can be added to existing code to confirm its correctness. In either case, they should be small, well-named, and fast, since ideally you will want to be able to run hundreds of them before pushing your changes into the project's shared code repository. + +.. note:: Developers often struggle with coming up with good names for their test classes and methods. As a starting point, the ASP.NET product team follows `these conventions `_ + +When writing unit tests, be careful you don't accidentally introduce dependencies on infrastructure. These tend to make tests slower and more brittle, and thus should be reserved for integration tests. You can avoid these hidden dependencies in your application code by following the `Explicit Dependencies Principle `_ and using :doc:`/fundamentals/dependency-injection` to request your dependencies from the framework. You can also keep your unit tests in a separate project from your integration tests, and ensure your unit test project doesn't have references to or dependencies on infrastructure packages. Creating Test Projects ---------------------- -Creating test projects using test runners. + +A test project is just a class library with references to a test runner and the project being tested (also referred to as the System Under Test or SUT). It's a good idea to organize your test projects in a separate folder from your SUT projects, and the recommended convention for DNX projects is something like this:: + + global.json + PrimeWeb.sln + src/ + PrimeWeb/ + project.json + Startup.cs + Services/ + PrimeService.cs + test/ + PrimeWeb.UnitTests/ + project.json + Services/ + PrimeService_IsPrimeShould.cs + +It is important that there be a folder/directory with the name of the project you are testing (PrimeWeb above), since the file system is used to find your project. + +Configuring the Test project.json +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The test project's ``project.json`` file should add dependencies on the test framework being used and the SUT project. For example, to work with the `xUnit test framework `_, you would configure the dependencies as follows: + +.. literalinclude:: unit-testing/sample/test/PrimeWeb.UnitTests/project.json + :language: json + :lines: 20-24 + :linenos: + :dedent: 2 + +As other test frameworks release support for DNX, we will link to them here. We are simply using xUnit as one example of the many different testing frameworks that can be plugged into ASP.NET and DNX. + +.. note:: Be sure the version numbers match for your project-to-project references. + +In addition to adding dependencies, we also want to be able to run the tests using a DNX command. To do so, add the following commands section to ``project.json``: + +.. literalinclude:: unit-testing/sample/test/PrimeWeb.UnitTests/project.json + :language: json + :lines: 25-27 + :linenos: + :dedent: 2 + +.. note:: Learn more about :doc:`/dnx/commands` in DNX. Running Tests ------------- -Running unit tests on the command line and in visual studio -Summary -------- -Summary +Before you can run your tests, you'll need to write some. For this demo, I've created a simple service that checks whether numbers are prime. One of the tests is shown here: + +.. literalinclude:: unit-testing/sample/test/PrimeWeb.UnitTests/Services/PrimeService_IsPrimeShould.cs + :language: c# + :lines: 18-27 + :linenos: + :dedent: 8 + +This test will check the values -1, 0, and 1 using the ``IsPrime`` method in each of three separate tests. Each test will pass if ``IsPrime`` returns false, and will otherwise fail. + +You can run tests from the command line or using Visual Studio, whichever you prefer. + +Visual Studio +^^^^^^^^^^^^^ + +To run tests in Visual Studio, first open the Test Explorer tab, then build the solution to have it discover all available tests. Once you have done so, you should see all of your tests in the Test Explorer window. Click Run All to run the tests and see the results. + +.. image:: unit-testing/_static/test-explorer.png + +If you click the icon in the top-left, Visual Studio will run tests after every build, providing immediate feedback as you work on your application. + +Command Line +^^^^^^^^^^^^ + +To run tests from the command line, navigate to your unit test project folder. Next, run:: + + dnx test + +You should see output similar to the following: + +.. image:: unit-testing/_static/dnx-test.png + +dnx-watch +^^^^^^^^^ + +You can use the ``dnx-watch`` command to automatically execute a DNX command whenever the contents of the folder change. This can be used to automatically run tests whenever files are saved in the project. Note that it will detect changes to both the SUT project and the test project, even when run from the test project folder. + +To use ``dnx-watch``, simply run it and pass it the command argument you would otherwise have passed to ``dnx``. In this case:: + + dnx-watch test + +With dnx-watch running, you can make updates to your tests and/or your application, and upon saving your changes you should see the tests run again, as shown here: + +.. image:: unit-testing/_static/dnx-watch.png + +One of the major benefits of automated testing is the rapid feedback tests provide, reducing the time between the introduction of a bug and its discovery. With continuously running tests, whether using ``dnx-watch`` or Visual Studio, developers can almost immediately discover when they've introduced behavior that breaks existing expectations about how the application should behave. + +.. tip:: View the `sample `_ to see the complete set of tests and service behavior. You can run the web application and navigate to ``/checkprime?5`` to test whether numbers are prime. You can learn more about testing and refactoring this checkprime web behavior in :doc:`integration-testing`. + +Additional Resources +-------------------- + +- :doc:`integration-testing` +- :doc:`/fundamentals/dependency-injection` + diff --git a/aspnet/testing/unit-testing/_static/dnx-test.png b/aspnet/testing/unit-testing/_static/dnx-test.png new file mode 100644 index 0000000000..ada15e2593 Binary files /dev/null and b/aspnet/testing/unit-testing/_static/dnx-test.png differ diff --git a/aspnet/testing/unit-testing/_static/dnx-watch.png b/aspnet/testing/unit-testing/_static/dnx-watch.png new file mode 100644 index 0000000000..abccc00eaa Binary files /dev/null and b/aspnet/testing/unit-testing/_static/dnx-watch.png differ diff --git a/aspnet/testing/unit-testing/_static/test-explorer.png b/aspnet/testing/unit-testing/_static/test-explorer.png new file mode 100644 index 0000000000..663b9e739c Binary files /dev/null and b/aspnet/testing/unit-testing/_static/test-explorer.png differ diff --git a/aspnet/testing/unit-testing/sample/NuGet.Config b/aspnet/testing/unit-testing/sample/NuGet.Config new file mode 100644 index 0000000000..95143bd09a --- /dev/null +++ b/aspnet/testing/unit-testing/sample/NuGet.Config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/aspnet/testing/unit-testing/sample/PrimeWeb.sln b/aspnet/testing/unit-testing/sample/PrimeWeb.sln new file mode 100644 index 0000000000..9250c1ec53 --- /dev/null +++ b/aspnet/testing/unit-testing/sample/PrimeWeb.sln @@ -0,0 +1,42 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{41484C4B-94D9-41D1-9848-A169673C01F2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FA6149DB-93D1-43DD-8230-676E8EA81C80}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + NuGet.Config = NuGet.Config + EndProjectSection +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PrimeWeb", "src\PrimeWeb\PrimeWeb.xproj", "{CAB6DEC1-CE2D-4932-B871-B4B8A7D3B06B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B23C32D2-95DF-4C95-91C6-2EA5591245E1}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PrimeWeb.UnitTests", "test\PrimeWeb.UnitTests\PrimeWeb.UnitTests.xproj", "{C76340B0-4F99-44FA-8420-0B2AE0CB64BF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CAB6DEC1-CE2D-4932-B871-B4B8A7D3B06B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAB6DEC1-CE2D-4932-B871-B4B8A7D3B06B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAB6DEC1-CE2D-4932-B871-B4B8A7D3B06B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAB6DEC1-CE2D-4932-B871-B4B8A7D3B06B}.Release|Any CPU.Build.0 = Release|Any CPU + {C76340B0-4F99-44FA-8420-0B2AE0CB64BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C76340B0-4F99-44FA-8420-0B2AE0CB64BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C76340B0-4F99-44FA-8420-0B2AE0CB64BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C76340B0-4F99-44FA-8420-0B2AE0CB64BF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {CAB6DEC1-CE2D-4932-B871-B4B8A7D3B06B} = {41484C4B-94D9-41D1-9848-A169673C01F2} + {C76340B0-4F99-44FA-8420-0B2AE0CB64BF} = {B23C32D2-95DF-4C95-91C6-2EA5591245E1} + EndGlobalSection +EndGlobal diff --git a/aspnet/testing/unit-testing/sample/global.json b/aspnet/testing/unit-testing/sample/global.json new file mode 100644 index 0000000000..0c71551c87 --- /dev/null +++ b/aspnet/testing/unit-testing/sample/global.json @@ -0,0 +1,6 @@ +{ + "projects": [ "src", "test" ], + "sdk": { + "version": "1.0.0-beta8" + } +} diff --git a/aspnet/testing/unit-testing/sample/src/PrimeWeb/PrimeWeb.xproj b/aspnet/testing/unit-testing/sample/src/PrimeWeb/PrimeWeb.xproj new file mode 100644 index 0000000000..a04b842fe2 --- /dev/null +++ b/aspnet/testing/unit-testing/sample/src/PrimeWeb/PrimeWeb.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + cab6dec1-ce2d-4932-b871-b4b8a7d3b06b + PrimeWeb + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + 51214 + + + \ No newline at end of file diff --git a/aspnet/testing/unit-testing/sample/src/PrimeWeb/Properties/AssemblyInfo.cs b/aspnet/testing/unit-testing/sample/src/PrimeWeb/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2999c5bc49 --- /dev/null +++ b/aspnet/testing/unit-testing/sample/src/PrimeWeb/Properties/AssemblyInfo.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PrimeWeb")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PrimeWeb")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("cab6dec1-ce2d-4932-b871-b4b8a7d3b06b")] diff --git a/aspnet/testing/unit-testing/sample/src/PrimeWeb/Services/PrimeService.cs b/aspnet/testing/unit-testing/sample/src/PrimeWeb/Services/PrimeService.cs new file mode 100644 index 0000000000..b181a15865 --- /dev/null +++ b/aspnet/testing/unit-testing/sample/src/PrimeWeb/Services/PrimeService.cs @@ -0,0 +1,23 @@ +using System; + +namespace PrimeWeb.Services +{ + public class PrimeService + { + public bool IsPrime(int candidate) + { + if(candidate < 2) + { + return false; + } + for(int divisor=2; divisor <= Math.Sqrt(candidate); divisor++) + { + if(candidate % divisor == 0) + { + return false; + } + } + return true; + } + } +} diff --git a/aspnet/testing/unit-testing/sample/src/PrimeWeb/Startup.cs b/aspnet/testing/unit-testing/sample/src/PrimeWeb/Startup.cs new file mode 100644 index 0000000000..225955571d --- /dev/null +++ b/aspnet/testing/unit-testing/sample/src/PrimeWeb/Startup.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Hosting; +using Microsoft.AspNet.Http; +using Microsoft.Dnx.Runtime; +using Microsoft.Framework.DependencyInjection; +using PrimeWeb.Services; + +namespace PrimeWeb +{ + public class Startup + { + // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + } + + public void Configure(IApplicationBuilder app, + IHostingEnvironment env) + { + // Add the platform handler to the request pipeline. + app.UseIISPlatformHandler(); + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.Run(async (context) => + { + if (context.Request.Path.Value.Contains("checkprime")) + { + int numberToCheck; + try + { + numberToCheck = int.Parse(context.Request.QueryString.Value.Replace("?","")); + var primeService = new PrimeService(); + if (primeService.IsPrime(numberToCheck)) + { + await context.Response.WriteAsync(numberToCheck + " is prime!"); + } + else + { + await context.Response.WriteAsync(numberToCheck + " is NOT prime!"); + } + } + catch + { + await context.Response.WriteAsync("Pass in a number to check in the form /checkprime?5"); + } + } + else + { + await context.Response.WriteAsync("Hello World!"); + } + }); + } + } +} diff --git a/aspnet/testing/unit-testing/sample/src/PrimeWeb/project.json b/aspnet/testing/unit-testing/sample/src/PrimeWeb/project.json new file mode 100644 index 0000000000..76f388b285 --- /dev/null +++ b/aspnet/testing/unit-testing/sample/src/PrimeWeb/project.json @@ -0,0 +1,28 @@ +{ + "webroot": "wwwroot", + "version": "1.0.0", + + "dependencies": { + "Microsoft.AspNet.IISPlatformHandler": "1.0.0-beta8", + "Microsoft.AspNet.Server.Kestrel": "1.0.0-beta8", + "Microsoft.AspNet.Diagnostics": "1.0.0-beta8" + }, + + "commands": { + "web": "Microsoft.AspNet.Server.Kestrel" + }, + + "frameworks": { + "dnx451": { }, + "dnxcore50": { } + }, + + "exclude": [ + "wwwroot", + "node_modules" + ], + "publishExclude": [ + "**.user", + "**.vspscc" + ] +} diff --git a/aspnet/testing/unit-testing/sample/src/PrimeWeb/wwwroot/web.config b/aspnet/testing/unit-testing/sample/src/PrimeWeb/wwwroot/web.config new file mode 100644 index 0000000000..8485f6719f --- /dev/null +++ b/aspnet/testing/unit-testing/sample/src/PrimeWeb/wwwroot/web.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/aspnet/testing/unit-testing/sample/test/PrimeWeb.UnitTests/PrimeWeb.UnitTests.xproj b/aspnet/testing/unit-testing/sample/test/PrimeWeb.UnitTests/PrimeWeb.UnitTests.xproj new file mode 100644 index 0000000000..8db4be6d34 --- /dev/null +++ b/aspnet/testing/unit-testing/sample/test/PrimeWeb.UnitTests/PrimeWeb.UnitTests.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + c76340b0-4f99-44fa-8420-0b2ae0cb64bf + PrimeWeb.UnitTests + ..\artifacts\obj\$(MSBuildProjectName) + ..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + + + + \ No newline at end of file diff --git a/aspnet/testing/unit-testing/sample/test/PrimeWeb.UnitTests/Properties/AssemblyInfo.cs b/aspnet/testing/unit-testing/sample/test/PrimeWeb.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..92eadf2bb2 --- /dev/null +++ b/aspnet/testing/unit-testing/sample/test/PrimeWeb.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PrimeWeb.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PrimeWeb.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c76340b0-4f99-44fa-8420-0b2ae0cb64bf")] diff --git a/aspnet/testing/unit-testing/sample/test/PrimeWeb.UnitTests/Services/PrimeService_IsPrimeShould.cs b/aspnet/testing/unit-testing/sample/test/PrimeWeb.UnitTests/Services/PrimeService_IsPrimeShould.cs new file mode 100644 index 0000000000..ff4bf894c4 --- /dev/null +++ b/aspnet/testing/unit-testing/sample/test/PrimeWeb.UnitTests/Services/PrimeService_IsPrimeShould.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using PrimeWeb.Services; + +namespace PrimeWeb.UnitTests.Services +{ + public class PrimeService_IsPrimeShould + { + private readonly PrimeService _primeService; + public PrimeService_IsPrimeShould() + { + _primeService = new PrimeService(); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + public void ReturnFalseGivenValuesLessThan2(int value) + { + var result = _primeService.IsPrime(value); + + Assert.False(result, String.Format("{0} should not be prime", value)); + } + + [Theory] + [InlineData(2)] + [InlineData(3)] + [InlineData(5)] + [InlineData(7)] + public void ReturnTrueGivenPrimesLessThan10(int value) + { + var result = _primeService.IsPrime(value); + + Assert.True(result, String.Format("{0} should be prime", value)); + } + + [Theory] + [InlineData(4)] + [InlineData(6)] + [InlineData(8)] + [InlineData(9)] + public void ReturnFalseGivenNonPrimesLessThan10(int value) + { + var result = _primeService.IsPrime(value); + + Assert.False(result, String.Format("{0} should not be prime", value)); + } + + } +} diff --git a/aspnet/testing/unit-testing/sample/test/PrimeWeb.UnitTests/project.json b/aspnet/testing/unit-testing/sample/test/PrimeWeb.UnitTests/project.json new file mode 100644 index 0000000000..a9ef26d25b --- /dev/null +++ b/aspnet/testing/unit-testing/sample/test/PrimeWeb.UnitTests/project.json @@ -0,0 +1,28 @@ +{ + "version": "1.0.0-*", + "description": "PrimeWeb.UnitTests Class Library", + "authors": [ "stevesmith (@ardalis)" ], + "tags": [ "" ], + "projectUrl": "", + "licenseUrl": "", + "frameworks": { + "dnx451": { }, + "dnxcore50": { + "dependencies": { + "Microsoft.CSharp": "4.0.1-beta-23409", + "System.Collections": "4.0.11-beta-23409", + "System.Linq": "4.0.1-beta-23409", + "System.Runtime": "4.0.21-beta-23409", + "System.Threading": "4.0.11-beta-23409" + } + } + }, + "dependencies": { + "PrimeWeb": "1.0.0", + "xunit": "2.1.0", + "xunit.runner.dnx": "2.1.0-beta6-build191" + }, + "commands": { + "test": "xunit.runner.dnx" + } +}