Data-Driven Testing & Fakes

Убедих се, че писането на юнит тестове не е толкова сложна задача, колкото си мислех, когато за първи път се сблъсках с тях. В курса по качествен програмен код имахме достатъчно възможности да се научим как да пишем тестове и тестваем код. Не след дълго обаче ми стана ясно, че класическият unit testing далеч не е идеален за всички възможни ситуации. Ако имаме обекти със сложно вътрешно състояние или произволно такова юнит тестовете много бързо заприличват на пача от инициализации. По-елегантното решение е, като разделим входните данни за теста от самия него и точно това е концепцията за Data-Driven Testing. Сега обаче нещата станаха с една идея по-сложни – вече имаме два етапа на писане на тест. Първият е дизайн на входните данни и вторият писане на теста + един парсър. За проектът, в който ще пиша тук имаше и малко усложнение.

Линк към играта – цък. В главната папка има файл с правилата, както и други маловажни неща като документация и тн.

Какъв е проблемът?

ItsAboutShimming

Като за начало – прост. Имаме private поле int[,] playField, което по принцип е рандом, а в тестовете трябва да му задаваме детерминистични стойности. Дотук добре – private поле се взима на един ред с помощта на рефлекшън, но аз исках да усложня малко сценария. Запълването на матрица от текстов файл не е сложно, но ако примерно трябваше да получаваме полето от API за връзка със сървър, който още не е готов сюжетът по тестването определено ще се разнообрази. Така, нещата вече бият към mocking и би било рационално да ползвам някой от вече популярните фреймуърци Rhino mocks или JustMock, но някъде мернах, че Visual Studio предоставя възможност за шимване на метод, а бях на вълна Java Script.

Нека бъде шим!

Планът за действие вече се избистри – правим 2 xml файла. Един с въведените данни и втори с очакваното състояние на полето след изигран ход. Шимваме метода private int[,] GeneratePlayField(int rows, int columns) в BalloonsEngine, за да му заменим поведението с такова по наше желание – да дълбае из харда и да зарежда каквото му кажем. След това играем ход със заредената конфигурация и сравняваме новото състояние с очакваното във вторият xml файл. Ако все още не е станало ясно – имам талант да изкарвам нещата извън пропорции.

Имплементация

AddFakes

Тук идва tricky частта. След като добавим референция към проекта си в тестовете, натискайки дясно копче върху нея има бутон Add Fakes Assembly. Това е нужно, за да генерира същите сигнатури на класове и методи, както в референцията. Уловката е, че го има само в Ultimate версията на Visual Studio, Ако не разполагате с нея – по-горе има две алтернативи. След това трябва да се добави неймспейса на новогенерираното асембли, както и още един:

using Microsoft.QualityTools.Testing.Fakes;
using Balloons_Pops_game.Fakes;

Следва кода с шим:


using (ShimsContext.Create())
 {
BalloonsEngine game = new BalloonsEngine(0, 0);

ShimBalloonsEngine.AllInstances.GeneratePlayFieldInt32Int32
    = (game1, x, y) => TestUtils.GenerateFieldShim(game, 5, 10);

    game = new BalloonsEngine(5, 10);
    game.TryPopBalloons(0, 0);

 }

Използването на тази using конструкция е задължително. ShimBalloonsEngine ни е един от генерираните фейк класове. В него има друг – AllInstances, в който се намират фейк методите. Ако натиснете F12 върху някой от тях ще видите, че те са всъщност Func<T, …> делегати със set. Сигнатурата им е почти същата като на оригиналните методи – връщат същият тип, но аргументите са с един повече. Добавен е нов първи аргумент, който е от типа на фейкнатия клас. Сетването става просто като му подадем ламбда експрешън със същата сигнатура на Func<>. Сиреч същият метод като оригиналния, но като му добавим още един аргумент отпред с типа на класа му. В моята имплементация това е метода GenerateFieldShim, който се намира в TestUtils. Останалите неща са тривиалностите по зареждане на xml, вземане на private поле и сравняване на резултати.

Това е по шимването на нашите методи. С този подход може да извратите и всеки друг метод в .Net. Начина е абсолютно същия – добавяме фейк асембли, using-и и шимваме с желаната функционалност. Все още не съм ползвал мокинг фреймуърк и не мога да направя сравнение между подходите, но ако трябва да го оплюя за финал ще кажа, че не ми допада синтаксиса и вътрешно се генерират дебели проекти от по 80мб, което на фона на 1мб солюшън без фейкове ми се струва странно.

Posted in C#, Programming Tagged with: , , , , , ,

Leave a Reply

Your email address will not be published.