Поддерживая приложения, построенные таким образом, что их модульное тестирование делает их сложными или труднопроходимыми, или невозможными, мы часто обращаемся к интеграционным тестам или вообще не проводим их. Вокруг вашей логики мы видим, что результаты, которые дает наш код, часто бывают сложными, ломкими и в большинстве случаев отнимают много времени, а этого просто не происходит. К счастью для всех нас, если в прошлом был случай, когда мы не смогли протестировать часть нашей бизнес-логики, обновление 2 для Visual Studio 2012 здесь, чтобы предложить ответ на ваши молитвы, о котором вы, возможно, просто не знали ,
Недавно я сменил работу с работы с внешними клиентами на творческую работу и на управление внутренней командой разработчиков. Это была другая сторона вселенной более чем одним способом наверняка.
С этим изменением обстановки изменились и типы проектов, над которыми я работал, с переходом на более бурную работу. Долой новое и старое … или что-то в этом роде.
Моя старая роль состояла из примерно 10% унаследованного проекта и 90% новых. Часто проектам с 10% заброшенных месторождений самим было всего менее 2 лет. Общее отношение к этому хлебу в моей команде всякий раз, когда им приходилось возвращаться к старому проекту, было чем-то вроде:
«…
О нет!
Вы имеете в виду, что я должен работать в .Net 3.5 …!
»
Нас, бедных, разработчиков Gen-Y …
Моя новая роль и это изменение в поддержке ряда старых приложений привели к необходимости искать способы адресации непроверенного, непроверяемого или черного ящика старого кода, чтобы повысить нашу уверенность в его рефакторинге.
Эта проблема
Writing unit testable code has been an ever growing trend in .Net, but 5-6 years ago it was still growing mass acceptance in our part of the world – partially lead by Microsoft themselves; Visual Studio 2008 Pro was the first IDE to have unit test support built in outside of the “team suite”, “ultimate” or “architect” editions. This gave the impression that “Unit tests were only for the rich”.
What I’ve seen in many workplaces over the years is that this lack of unit test penetration lead to a lot of .Net developers simply not learning good practices when it comes to separation of concerns and modular coding practices that lead to testable code. This isn’t a complaint. We were all there once; just a statement that I saw the effects of this in a lot of legacy projects that all suffered from:
- Wide spread usage of static classes and methods (i.e. no interfaces, virtual methods etc).
- A lack of dependency injection (no way to easily replace functionality with a mock or a stub).
- Tightly coupled code (a domain class that talks to the database, or a business layer method that refers to HttpContext.
This lead to scenarios that meant that when developers on .Net started out unit testing, they often were forced into doing end-to-end integration tests to get around their tightly coupled architecture.
[TestMethod] public void UserSignup_ValidFormSubmission_UserIsCreated() { var testHelper = loadApplicationOverHttp(Configuration.LocalSiteUrl); testHelper.SubmitSignupDataOverHttp("username", "password"); var dbEntry = DataLayer.GetUser("username"); Assert.IsNotNull(dbEntry, "No user was created in the database"); }
From a build and automation perspective, this type of testing is expensive as it takes a long time to build and run tests. To start with you need to have a database server online just to run your tests. You often need a clean database. It needs to be kept in synch with production or a known state. When working with CI it’s harder to automate, and hell when you’re just a new developer on the team you’ve got to work hard to get your workspace into a happy place to even run these kinds of integration tests.
This lack of smooth experience only leads to one thing: less testing.
While Integration tests are useful to know that your application works end-to-end, obviously the fiddly and brittle nature of them is why Unit tests are such a preferred method of testing code path functionality across your entire codebase. But Unit Testing takes architecture choices from day 1 to make your applications easily testable, so brownfield projects caught out with a lack of good architecture with little budget for refactoring work often flounder in a unit testing no mans land.
That was until Microsoft Moles came along.
With Moles came a huge power of interception that was originally a free piece of framework/tooling from Microsoft Research that worked with Visual Studio 2010, albeit lacking some polish.
Sadly Microsoft took Moles away from us with the launch of Visual Studio 2012. In a similar turn to how they approached VS 2005 they opted to only include support for Moles in the Ultimate edition.
That was until the Update 2 for Visual Studio 2012 released last week, which if you download today has all of the
fakes magic (they changed the name), but now with Visual Studio 2012 support and awesome IDE integration.
moles
Microsoft Moles/Fakes
One of the greatest things about unit testing in other more dynamic languages like Ruby or Python is that testing logic is inherently easier because you can simply replace functionality at runtime as they’re dynamic languages. Mocking is almost a non-event just because of this dynamic nature.
Thanks to Fakes some of this power is yours to tap into with VS 2012 just as it was when they first released Moles in 2010.
Moles has grown into what we know now as Microsoft Fakes and is an incredibly powerful bit of kit for allowing you to replace functionality for your application at runtime. This power then allows you to intercept legacy code and make it testable.
A good example of this power is in the example below of a shim of System.DateTime.Now and its Get accessor. Microsoft Fakes allows me to test a static method or anything that depends on it with ease; in this case changing the date to 1st January 1970:
[TestMethod] public void MyDateProvider_ShouldReturnDateAsJanFirst1970() { using (Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create()) { // Arrange: System.Fakes.ShimDateTime.NowGet = () => new DateTime(1970, 1, 1); //Act: var curentDate = MyDateTimeProvider.GetTheCurrentDate(); //Assert: Assert.IsTrue(curentDate == new DateTime(1970, 1, 1)); } } public class MyDateTimeProvider { public static DateTime GetTheCurrentDate() { return DateTime.Now; } }
Pretty cool!?
And to get this level of interception all you need to do to get this is download Visual Studio 2012 Update 2, thenRight click on a Reference in your Unit Test Project and add a Fakes Assembly (a shimmed copy of your binary).
Then all you need to do is:
- Wrap your logic in a ShimsContext.
- Use the new convention based syntax “Shim[Classname]” class and “PropertyName[Get/Set]” syntax to define what your shim will do.
- Go grab a <insert tasty beverage of your choosing/>
And you’re off and away!
Stop reading – Start testing the untestable!
With the power of Microsoft Fakes offered in Visual Studio 2012 Update 2 there is now no excuse not to test that old .Net project you and your team members are afraid to refactor. Get to it.