ASP. NET series: unit test and asp.net unit test

Source: Internet
Author: User
Tags nopcommerce xunit

ASP. NET series: unit test and asp.net unit test

Unit Testing can effectively improve our work efficiency and quality in coding, design, debugging, reconstruction, and other aspects. There are many open-source projects available for reference and learning on github. Most projects related to NopCommerce, Orchard, and Microsoft's asp.net mvc and entity framework can be used as reference for learning unit testing.. Unit Test path (C #) and. NET unit test ArtAndC # Test-driven developmentThey are all good learning materials.

1. Benefits of Unit Testing

(1) unit test help design

Unit testing forces us to shift from focusing on implementation to focusing on interfaces. The process of writing unit tests is the process of designing interfaces, and the process of passing unit tests is the process of writing and implementing them. I always think this is the most important benefit of unit testing. Let's focus on the Interface rather than the implementation details.

(2) unit test help code

Application unit testing will allow us to actively eliminate and reduce unnecessary coupling. Although the starting point may be to facilitate the completion of unit testing, the results are usually more of a type of responsibility, the coupling between types is significantly reduced. This is a known Effective Way to Improve the coding quality and an effective way to improve the coding level of developers.

(3) unit test help debugging

The unit test code can be applied to quickly locate the source of the problem during debugging.

(4) unit test help refactoring

Refactoring of existing projects is a better choice from compiling unit tests. First, refactor the local code, extract the interface for unit testing, and then refactor the type and level.

Unit Tests are designed, coded, and debugged to make them essential skills for software developers.

2. Application Unit Test

Unit testing is not easy to understand and use testing and simulation frameworks like XUnit and Moq. First, you must have a sufficient understanding of the Code to be compiled. Generally, we regard the code as some statically correlated types. The dependency between types uses interfaces to implement class implementation interfaces. At runtime, we use custom factories or dependency injection containers for management. A unit test usually calls the method or attribute to be tested in a method and detects the running result of the method or attribute by using Assert assertions, usually we need to write the following test code.

(1) testing Domain Layer

The domain layer is composed of POCO and can directly test the public behaviors and attributes of the domain model. The following code extracts a unit test segment from the Customer entity:

(2) Test the application layer

The application layer is mainly composed of service interfaces and implementations. the dependency of the application layer on infrastructure components exists as interfaces, and these infrastructure interfaces are simulated using Mock.

(3) Test the presentation layer

The dependency of the presentation layer on the application layer is manifested in the call to the service interface, and the instance of the dependent interface is obtained through Mock.

(4) Test the infrastructure layer

Testing at the infrastructure layer usually involves the configuration file, Log, HttpContext, SMTP, and other system environments. The Mock mode is usually used.

(5) use unit testing for Integration Testing

First, the system obtains the interface instance through interface dependency and dependency injection container. When configuring the dependency, the implemented part is directly configured, and the pseudo-implemented part is configured as the instance object generated by the Mock framework. With the continuous implementation of the system, the Mock object dependent on the configuration is constantly replaced with the Implementation object.

3. Use Assert to determine logical behavior correctness

Assert is the core class in the unit test framework. In the unit test method, the static method of the Assert class verifies the running result of the method or attribute to be tested to determine whether the logical behavior is correct. The shocould method is generally packaged as an Assert provided in the form of an extension method.

(1) Assert assertions

If you have used System. diagnostics. contracts. the Assert method of Contract makes it easier for Assert static classes provided in unit test frameworks such as XUnit, and it is also conditional judgment, the Assert class in the unit test framework provides a large number of more specific methods, such as Assert. true, Assert. notNull, Assert. equal facilitates condition judgment and information output.

(2) shocould Extension Method

Using the shocould extension method not only reduces the use of parameters, but also enhances the semantics, and provides more friendly prompts when the test fails. Xunit. shocould has stopped updating. The shocould component has reused the Assert Implementation of Xunit, but has also stopped updating. The Shouldly component uses its own implementation. It is a project that is still being updated. structuremap uses Shouldly in unit testing. It is also easy to manually wrap the Assert. The following code extracts the custom Extension Method of the NUnit Assert in NopComnerce 3.70.

namespace Nop.Tests{    public static class TestExtensions    {        public static T ShouldNotNull<T>(this T obj)        {            Assert.IsNull(obj);            return obj;        }        public static T ShouldNotNull<T>(this T obj, string message)        {            Assert.IsNull(obj, message);            return obj;        }        public static T ShouldNotBeNull<T>(this T obj)        {            Assert.IsNotNull(obj);            return obj;        }        public static T ShouldNotBeNull<T>(this T obj, string message)        {            Assert.IsNotNull(obj, message);            return obj;        }        public static T ShouldEqual<T>(this T actual, object expected)        {            Assert.AreEqual(expected, actual);            return actual;        }        ///<summary>        /// Asserts that two objects are equal.        ///</summary>        ///<param name="actual"></param>        ///<param name="expected"></param>        ///<param name="message"></param>        ///<exception cref="AssertionException"></exception>        public static void ShouldEqual(this object actual, object expected, string message)        {            Assert.AreEqual(expected, actual);        }        public static Exception ShouldBeThrownBy(this Type exceptionType, TestDelegate testDelegate)        {            return Assert.Throws(exceptionType, testDelegate);        }        public static void ShouldBe<T>(this object actual)        {            Assert.IsInstanceOf<T>(actual);        }        public static void ShouldBeNull(this object actual)        {            Assert.IsNull(actual);        }        public static void ShouldBeTheSameAs(this object actual, object expected)        {            Assert.AreSame(expected, actual);        }        public static void ShouldBeNotBeTheSameAs(this object actual, object expected)        {            Assert.AreNotSame(expected, actual);        }        public static T CastTo<T>(this object source)        {            return (T)source;        }        public static void ShouldBeTrue(this bool source)        {            Assert.IsTrue(source);        }        public static void ShouldBeFalse(this bool source)        {            Assert.IsFalse(source);        }        /// <summary>        /// Compares the two strings (case-insensitive).        /// </summary>        /// <param name="actual"></param>        /// <param name="expected"></param>        public static void AssertSameStringAs(this string actual, string expected)        {            if (!string.Equals(actual, expected, StringComparison.InvariantCultureIgnoreCase))            {                var message = string.Format("Expected {0} but was {1}", expected, actual);                throw new AssertionException(message);            }        }    }}
4. Use a pseudo object

Pseudo objects can solve the problem of external dependencies that cannot be tested in the code to be tested. More importantly, they implement low coupling through interface abstraction. For example, using the abstract IConfigurationManager interface to use the ConfigurationManager object seems to only add more code for unit testing, in fact, we usually do not care whether the subsequent configuration is a config file read through the ConfigurationManager static class. We only care about the configuration values. In this case, using IConfigurationManager can depend on the specific ConfigurationManager type, you can also use other implementation classes that implement the IConfigurationManager interface when the system needs to be extended.

The main steps for using pseudo objects to solve external dependencies are as follows:

(1) Replace the original type dependency with interface dependency.

(2) implement the above interface by adapting to the original type.

(3) manually create an interface implementation class for unit testing or use the Mock framework to generate an interface instance during unit testing.

The manually created implementation class fully implements the interface, so that the implementation class can be used in multiple tests. You can use the Mock framework to generate an instance of the corresponding interface. You only need to simulate the method to be called in the current test. Generally, you need to make logical judgments based on the parameters and return different results. Both the simulated object manually implemented and the pseudo object generated by Mock are called the pile object, that is, the Stub object. The essence of the Stub object is the pseudo object of the interface that the test class depends on. It ensures that the tested class can be normally called by the test code.

Solved the dependency problem of the tested class and solved the problem that Assert assertions cannot be used directly on the tested method. In this case, we need to use Assert on another type of pseudo object. Generally, the simulated object used by Assert is called a simulated object, that is, a Mock object. Mock objects are essentially provided for Assert for verification, which ensures that the classes to be tested can be verified normally when assertions cannot be directly used.

Both Stub and Mock objects are pseudo objects, that is, Fake objects.

The distinction between Stub and Mock objects is very simple. From the perspective of the tested class, Stub objects and Assert Mock objects. However, even if you do not understand the meanings and differences, there will be no problems in use. For example, if the test email is sent, we usually cannot apply Assert directly to the tested code. We will apply Assert to the simulated STMP Server Object to determine whether the email is successfully received, the SMTPServer simulated object is a Mock object instead of a Stub object. For example, to write logs, we can apply Assert directly to the ILogger interface to determine whether the operation is successful. At this time, the Logger object is the Stub object and the Mock object.

5. Common Unit Test frameworks and components

(1) unit test framework.

XUnit is currently the most popular. NET unit testing framework. NUnit has been widely used earlier, such as nopCommerce and Orchard. NUnit has been used since the beginning. XUnit is currently a better choice than NUnit. from github, we can see that a series of Microsoft projects such as asp.net mvc use the XUnit framework.

(2) Mock framework

Moq is currently the most popular Mock framework. Microsoft projects such as Orchard and asp.net mvc use Moq. NopCommerce uses Rhino Mocks. NSubstitute and FakeItEasy are two Mock frameworks widely used.

(3) The Mock component netDumbster sent by mail

You can use nuget to obtain the netDumbster component, which provides SimpleSmtpServer objects for simulating the mail sending environment.

Generally, we cannot directly use Assert for mail sending. With netDumbster, we can apply Assert to the mail received by the simulation server.

public void SendMailTest(){    SimpleSmtpServer server = SimpleSmtpServer.Start(25);    IEmailSender sender = new SMTPAdapter();    sender.SendMail("sender@here.com", "receiver@there.com", "subject", "body");    Assert.Equal(1, server.ReceivedEmailCount);    SmtpMessage mail = (SmtpMessage)server.ReceivedEmail[0];    Assert.Equal("sender@here.com", mail.Headers["From"]);    Assert.Equal("receiver@there.com", mail.Headers["To"]);    Assert.Equal("subject", mail.Headers["Subject"]);    Assert.Equal("body", mail.MessageParts[0].BodyData);    server.Stop();}

(4) HttpSimulator, a Mock component of HttpContext

You can also get it through nuget and initiate an Http request using the HttpSimulator object. The HttContext object is available in its lifecycle.

Since HttpContext is closed and Moq cannot be used for simulation, we usually use the following code snippet:

private HttpContext SetHttpContext(){    HttpRequest httpRequest = new HttpRequest("", "http://mySomething/", "");    StringWriter stringWriter = new StringWriter();    HttpResponse httpResponse = new HttpResponse(stringWriter);    HttpContext httpContextMock = new HttpContext(httpRequest, httpResponse);    HttpContext.Current = httpContextMock;    return HttpContext.Current;}

After using HttpSimulator, we can simplify the Code as follows:

using (HttpSimulator simulator = new HttpSimulator()){  }

It is important to test the DbContext life cycle of programs using IoC containers and EntityFramework. The Life Cycle of DbContext must be consistent with that of HttpRequest. Therefore, it is necessary to test the life cycle of IoC containers.

6. Difficulties in Unit Testing

(1) unwilling to pay learning costs and change existing development habits.

(2) I am not thinking about unit testing as a framework.

(3) The unit test is applied only after the project, that is, the benefits of unit test cannot be obtained, and the unit test is misunderstood because the code test is unfriendly.

(4) refuse to consider efficiency, scalability, and decoupling, and only consider the implementation of data and functions.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.