Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
signalr / SignalR Programming in Microsoft ASP.NET.pdf
Скачиваний:
65
Добавлен:
25.05.2015
Размер:
19.23 Mб
Скачать

The code speaks for itself. IClock instances will be resolved as singletons at application level, whereas IMessageFormatter objects will be instantiated for each request. Moreover, in this case, it is not necessary to register the hub because, by default, when asked for a specific class, Ninject will be in charge of automatically creating instances of it without needing prior registration.

For more information about the use of this component, you can review the official product documentation available at http://www.ninject.org/learn.html.

Unit testing with SignalR

The full extent of unit testing is beyond the scope of this book, but we will do a quick overview of it for those readers who are not yet acquainted with it so that they can at least get an idea of what we will be looking at in the following pages.

Basically, unit tests are small pieces of code whose mission is to ensure the proper functioning of other parts of the code. In a periodical and automated way, they ensure that each component works as we expected it to work when it was implemented, so they add great robustness and maintainability to our applications.

For example, if in the future someone decides to implement new features, we could detect with unit tests whether the changes introduced have unwanted side effects on existing components. Or, if we need to modify the implementation of one of those components, unit testing would allow us to see whether everything still works as before after applying the changes. In large and complex applications, with a long life and where many developers participate, unit testing provides invaluable safety and soundness.

Let’s look at a quick example. Imagine the following method, which returns a number based on another one that we send to it as an argument, applying an algorithm that an expert in the domain of our application has given us:

public int GetNumber(int a)

{

if (a > 10) return a*10; if (a < 5) return -a; return a*2;

}

If this method were to be used from hundreds of points of our application, any change in its logic could have a great impact on our system. Therefore, we would want to implement unit tests to ensure throughout the life of our software that this method will always do what all the components that consume it expect it to do.

The following code shows unit tests performed on the preceding code. Note that these are simple methods that use the code tested, sending it different arguments and checking that it reacts as

Advanced topicsChapter 9

205

www.it-ebooks.info

expected. The tests are written using the framework integrated in all Visual Studio editions4, and they cover all possible paths that can be followed in the execution of the function (100 percent coverage of the code to be checked):

[TestClass]

public class DomainGetNumberTests

{

[TestMethod]

public void GetNumber_Returns_10xA_if_A_is_greater_than_10()

{

// Arrange

var domain = new Domain(); var value = 12;

var expected = 120;

// Act

var result = domain.GetNumber(value);

// Assert Assert.AreEqual(expected, result);

}

[TestMethod]

public void GetNumber_Returns_Minus_A_if_A_is_less_than_5()

{

// Arrange

var domain = new Domain(); var value = 4;

var expected = -4;

// Act

var result = domain.GetNumber(value);

// Assert Assert.AreEqual(expected, result);

}

[TestMethod]

public void GetNumber_Returns_2xA_If_A_Is_In_Range_5_To_10()

{

// Arrange

var domain = new Domain(); var value = 5;

var expected = 10;

// Act

var result = domain.GetNumber(value);

// Assert Assert.AreEqual(expected, result);

}

}

4 There are other testing frameworks, such as XUnit, NUnit, or MbUnit, which even have total integration with Visual Studio. Although they are different in some details, all the concepts that we will see here can be applied to any of them.

206 Chapter 9Advanced topics

www.it-ebooks.info

The classes containing tests are marked with the [TestClass] attribute, and test methods are decorated using [TestMethod]. Each one tests a different aspect of the component, and they often have long and very descriptive names. For example, in the preceding code we see three different tests, one for each of the possible paths that execution could take in the method that we are testing.

Obviously, they could be more exhaustive and check more values in each case, but these tests could be a good starting point.

Unit tests are typically created as independent projects, exclusively entrusted with this task. Thus, we will not mix the code of the original project with its testing code.

Another detail that can be observed in the preceding code is that its implementation follows the structure “AAA” (Arrange, Act, Assert), which refers to the encoding preparations, the execution of the test itself, and the verification of the results.

When running tests from Visual Studio, we obtain a satisfactory result, as shown in Figure 9-4. We can perform these tests manually at any time or automate them within the build process of the application.

FIGURE 9-4  Satisfactory result of unit testing.

Now let’s imagine that, due to a new need of our application, a developer tweaks the method, slightly modifying its behavior in this way:

public int GetNumber(int a)

{

if (a > 10) return a * 10;

if (a < 5) return -a;

return a * 5; // Changed!

}

If we have configured Visual Studio to do so, when the project was built, unit tests would be executed and compilation would fail because one of these tests would not be passed—the one which ensured that for inputs between 5 and 10 the return should be the argument multiplied by two. See Figure 9-5.

Advanced topicsChapter 9

207

www.it-ebooks.info

FIGURE 9-5  Incorrect result in unit tests.

At this point, the developer who made the change might realize that her small modification can affect many more points than she had previously estimated, and so rethink it or explore other alternatives. This is the security that unit tests provide: if anything changes from what we had expected at the time of building, the alarms go off.

One of the advantages of unit tests, which often turns into a problem, is that they must check a single component of the system (hence the “unit” in the name), and this is more complicated if the code to be tested uses external dependencies—that is, other components.

Consider the following code, which in the previous case we could check in a very direct way. If any test failed, we knew exactly where we needed to correct it. However, this time, a unit test failure

could be either due to an incorrect implementation of the GetNumber() method or to an error in the Calculator class—or, in turn, in a dependence of the latter:

public int GetNumber(int a)

{

var calc = new Calculator(); if (a > 10)

return calc.Multiply(a, 10); if (a < 5)

return calc.Multiply(a, -1); return calc.Multiply(a, 2);

}

The preceding test would be performing a check on not only the GetNumber() method but also its dependencies. Such tests are called “integration tests” because they test how the individual components are integrated with each other, and they are usually a step taken after unit tests are performed.

Therefore, to perform correct unit tests, we should ideally not use dependencies or, if we need to, at least not have coupling as strong as the one shown in the preceding example, where the

Calculator object was instantiated directly from the method tested. Forget coupling; that was more like welding.

We have already discussed the techniques that we use to decouple components, so we will not go over them again. Just keep in mind that to perform unit tests easily, low cohesion will always be a

208 Chapter 9Advanced topics

www.it-ebooks.info

great ally. Thus, applying these techniques, we could have implemented our component without any coupling:

public class Domain

{

private ICalculator _calculator; public Domain(ICalculator calculator)

{

_calculator = calculator;

}

public int GetNumber(int a)

{

if (a > 10)

return _calculator.Multiply(a, 10); if (a < 5)

return _calculator.Multiply(a, -1); return _calculator.Multiply(a, 2);

}

}

When freed from the instance of Calculator, what we use is an abstraction that isolates us from the specific implementation of the calculator that we are going to use. From unit tests, we can properly supply appropriately prepared dependencies to help us check the operation of the GetNumber() method exclusively, which is what the test is about, without having the result affected by the operation of Calculator, as was previously the case.

To supply these dependencies, mock objects are often used—minimal implementations of the contracts, whose behavior is defined by the tests themselves. That is, they are classes that will implement the interfaces required by the components to be tested, but their code will be exclusively dedicated to facilitate testing. Sometimes, we will find empty implementations; at other times they will emulate production environments (for example, a database emulated in memory for testing). We might find partial implementations oriented only to the test being performed, and sometimes we will predefine the behavior of classes dynamically.

There are frameworks designed exclusively to create and prepare the behavior of these mock objects, such as Moq, Rhino Mocks, Fakes, JustMock, FakeItEasy, TypeMock, and others. Moq is, by far, the one most used for its simplicity and power.

The best way to understand how to work with these tools is to look at some code. The following method uses Moq to dynamically create an object that implements the ICalculator interface and to set the behavior that we want for its Multiply() method. Note that this is about configuring only the behavior that we need to pass the tests that we will perform on the GetNumber() method. It is also not necessary (nor desirable!) to replicate the logic of the original method in this configuration; we simply need to make it so that the desired result is obtained for given input arguments:

using Moq; // Requires NuGet package "Moq"

...

[TestMethod]

public void GetNumber_Returns_10xA_if_A_is_greater_than_10()

{

Advanced topicsChapter 9

209

www.it-ebooks.info

// Arrange

var mock = new Mock<ICalculator>();

mock.Setup(c => c.Multiply(It.IsAny<int>(), It.IsAny<int>()))

.Returns((int a, int b) => a * b); var fakeCalculator = mock.Object;

var domain = new Domain(fakeCalculator); var value = 12;

var expected = 120;

// Act

var result = domain.GetNumber(value);

// Assert Assert.AreEqual(expected, result);

}

When configuring the mock object, we indicate that the result for calls to the Multiply() method using any integers as inputs will be the product of both. We could have also set results for specific inputs, or even ranges of inputs, and defined constant returns or returns with any value.

This mock object created and configured with Moq is injected into the constructor of the Domain class instance, so it will be the calculator used internally from its GetNumber() method. Thus, because we are controlling the behavior of dependencies with this test, we can ensure that if something goes wrong, it will mean that the GetNumber() method is the one that is incorrectly implemented.

Moq, like the other frameworks, has a good set of tools to configure the behavior of these mock objects as well as to check that they have been properly used. For example, the following code allows ensuring at the end of the test that the Multiply() method of the dependency has been called exactly once when invoking domain.GetNumber():

using Moq;

...

// Act

var result = domain.GetNumber(value);

// Assert Assert.AreEqual(expected, result); mock.Verify(

calc => calc.Multiply(It.IsAny<int>(), It.IsAny<int>()), Times.Once()

);

In the Moq project wiki5, you can view the syntax for setting up mock objects as well as many examples of their use. It is highly recommended reading for beginners in the use of this framework.

5 Moq wiki: http://code.google.com/p/moq/wiki/QuickStart

210 Chapter 9Advanced topics

www.it-ebooks.info

Соседние файлы в папке signalr