Створення програми на WPF з використанням принципів TDD. Частина 1, Різне, Програмування, статті

У цій частині ми створимо скелет нашого застосування. І перше питання, яке необхідно вирішити, це визначитися з назвою програми. Є open-source проект WixEdit, схожий за призначенням. Але ми на створення і редагування проекту не замахуємося, ми будемо тільки відображати існуючий проект, і основна функція буде збірка дистрибутива. Після недовгих роздумів приймаємо рішення назвати проект WixMaker. Створюємо в Visual Studio новий проект WixMaker з шаблону WPF Application і додаємо в нього посилання на складання Composite Application Library. По кроках процес створення проекту з використанням CAL описаний в How to: Create a Solution Using the Composite Application Library.


Також під’єднуємо проект до системи контролю версій, наприклад SVN


Це буде головний підпроект, який буде завідувати тільки складанням модулів і запуском основного вікна програми. Всі ці дії буде виконувати клас Bootstrapper. Основна функціональність буде розбита на слабосвязанних модулі. Створимо макет зовнішнього вигляду програми, а точніше розіб’ємо вміст головного вікна на регіони. Мінімальний набір це меню і основна частина, де виводиться вміст проекту. У першому наближенні компоновка вікна наступна:


<Window x:Class=”WixMaker.Shell”


    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”


    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”


    xmlns:cal=”clr-namespace:Microsoft.Practices.Composite.Wpf.Regions;assembly=Microsoft.Practices.Composite.Wpf”


    Title=”WixMaker” Height=”400″ Width=”600″>


    <Grid>


        <Grid>


            <Grid.RowDefinitions>


                <RowDefinition Height=”Auto” />


                <RowDefinition Height=”*” />


            </Grid.RowDefinitions>


            <ItemsControl Name=”MenuRegion” cal:RegionManager.RegionName=”MenuRegion” />


            <ContentControl Name=”MainRegion” cal:RegionManager.RegionName=”MainRegion” Grid.Row=”1″ />


        </Grid>


    </Grid>


</Window>


Поки на цьому зупинимося і займемося модулем. Створимо модуль, який буде завідувати показом вмісту WiX проекту та обробкою команд з меню. Робимо новий підпроект WixProject і в ньому клас WixProjectModule. Він буде служити основним сполучною ланкою для зв’язку вмісту модуля із зовнішнім світом. Тепер ми успадковуємо клас WixProjectModule від інтерфейсу IModule, і за допомогою IntelliSense створюємо реалізацію єдиного методу цього інтерфейсу Initialize.


#region IModule Members


public void Initialize()


{


    throw new NotImplementedException();


}


#endregion


Також пропишемо наш модуль в завантажувачі.


public class Bootstrapper : UnityBootstrapper


{


    protected override DependencyObject CreateShell()


    {


        Shell shell = Container.Resolve<Shell>();


        shell.Show();


        return shell;


    }


    protected override IModuleEnumerator GetModuleEnumerator()


    {


        return new StaticModuleEnumerator().


            AddModule(typeof(WixProjectModule));


    }


}


Це останній крок, який ми може зробити без тестів. Далі створюємо тест для WixProjectModule для методу Initialize, скориставшись підтримкою Visual Studio Team System. Клацнемо на назві функції Initialize () і з контекстного меню виберемо пункт “Create Unit Tests …”.


Погодимося з підтверджуючим діалогом, а в діалозі створення нового проекту змінимо назву проекту на WixProject.Test. Тут ми слідуємо методикою Microsoft, і називаємо підпроект з тестами на ім’я підпроекту, який він тестує, з додаванням “. Test” в назву. Далі студія створить проект, клас тесту для WixProjectModule і тестовий метод InitializeTest () і початковим вмістом.


/// <summary>


///A test for Initialize


///</summary>


[TestMethod()]


public void InitializeTest()


{


    WixProjectModule target = new WixProjectModule(); // TODO: Initialize to an appropriate value


    target.Initialize();


    Assert.Inconclusive(“A method that does not return a value cannot be verified.”);


}


Мені дуже подобається підтримка з боку Visual Studio в даному випадку. По-перше, пророблена велика рутинна робота, а по-друге, в сгенеренний коді немає жодної зайвої строчки.


Тепер треба вирішити, що ми будемо реалізовувати і відповідно перевіряти. Підемо по шляху найменших зусиль і додамо меню. Так як після додавання меню має з’явитися в регіоні MenuRegion, то давайте будемо перевіряти, що там щось з’явилося. Але для цього треба зробити кілька підготовчих операцій. Для початку треба звідкись отримати посилання на RegionManager, куди ми будемо додавати меню. Це робиться просто:


private readonly IRegionManager _regionManager;


public WixProjectModule(IRegionManager regionManager)


{


    _regionManager = regionManager;


}


Не дивуйтеся, але цього буде достатньо. Якщо ви поставите точку зупину в конструкторі і запустіть проект, то побачите, що regionManager містить реальний об’єкт. Перший раз, побачивши такий код в висмикнутою з надр інтернету прикладі, я нічого не зрозумів. Звідки візьметься реалізація IRegionManager? Власне це і стало причиною спонукальної познайомитися з Composite Application Library. Тут ми вперше раз в нашому проекті стикаємося з паттерном Inversion of Control (IoC) який в даному випадку реалізований через інший, більш спеціалізований патерн Dependency Injection (DI). А це означає, що всю необхідну роботу для нас робить Unit Container. Крім того що ми уникли внесення сильного зв’язку в наш модуль, а то й ще гірше Singleton, ми отримали чудову можливість для модульного тестування, чим ми зараз і скористаємося. Для цього в нашому тесті замість реального RegionManager ми передамо підроблений об’єкт (Mock object) з необхідною нам для тесту функціональністю.


public class MockRegionManager : IRegionManager


{


    private Dictionary<string, IRegion> _regions = new Dictionary<string, IRegion>();


    public IDictionary<string, IRegion> Regions


    {


        get { return _regions; }


    }


    public void AttachNewRegion(object regionTarget, string regionName)


    {


        throw new NotImplementedException();


    }


    public IRegionManager CreateRegionManager()


    {


        throw new NotImplementedException();


    }


}


Як бачите, код підробки простий. І він і не повинен бути складним. Ми не намагаємося повністю зімітувати поведінку об’єкта, а тільки ту мінімальну частину, яка потрібна нам для конкретного тесту. Якщо для іншого тесту нам знадобиться додаткова функціональність, треба буде оцінити, що краще: дописати нову функціональність у вже існуючий підроблений об’єкт, з імовірністю, що це торкнеться вже налагоджені тести. Або написати нову реалізацію підробленого об’єкта, тільки з тією функціональністю, яка потрібна для нового тесту. Подібним чином реалізуємо підроблений об’єкт для IRegion. Тепер у нас є все для першого тесту.


Повертаємося до нашого тесту, а конкретно до рядку з TODO, який пропонує нам дописати код ініціалізації. Як бачимо, коментар був поставлений не дарма. Замінюємо цей рядок на


var regionManager = new MockRegionManager();


WixProjectModule target = new WixProjectModule(regionManager);


Також додаємо підроблений регіон для меню


var menuRegion = new MockRegion();


regionManager.Regions.Add(“MenuRegion”, menuRegion);


З инициализирующее частиною тесту ми закінчили, займемося перевіркою результату. Модуль повинен додати нерухомість меню в регіон “MenuRegion”. В деталізацію про тип об’єкта я не хочу вдаватися, тому буде достатньо перевірки, що якийсь об’єкт доданий в регіон.


Assert.AreEqual(1, menuRegion.AddedViews.Count);


Повністю тест виглядет так


[TestMethod]


public void InitializeTest()


{


   var regionManager = new MockRegionManager();


   WixProjectModule target = new WixProjectModule(regionManager);


   var menuRegion = new MockRegion();


   regionManager.Regions.Add(“MenuRegion”, menuRegion);


   target.Initialize();


   Assert.AreEqual(1, menuRegion.AddedViews.Count);


}


Тест готовий. Нам треба його запустити, щоб отримати початковий негативний результат. Це важливий момент. Якщо ви спочатку напишіть функціональність, а потім тест, і він відразу пройде, то це не означає, що новий шматок коду працює правильно. Може бути, тест проходить і без нього. Запустити тест можна або з контекстного меню, вставши всередину тестового методу і вибравши “Run Tests” або через меню Test-> Run-> Test In Current Context.Получаем перший результат


Повністю Error Message:


Test method WixProject.Test.WixProjectModuleTest.InitializeTest threw exception:  System.NotImplementedException: The method or operation is not implemented..


Ну що ж, ми отримали червоний колір і конкретне повідомлення, яке вказує що робити далі. Створюємо MainMenu клас, отнаследованний від UserControl.


<UserControl x:Class=”WixProject.MainMenu”


    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”


    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”>


    <Grid>


        <Menu Grid.Row=”0″ IsMainMenu=”True”>


            <MenuItem Header=”File”>


                <MenuItem Header=”Open” />


            </MenuItem>


        </Menu>


    </Grid>


</UserControl>


Нарешті добралися до суті і додаємо наше меню в регіон


public void Initialize()


{


   IRegion menuRegion = _regionManager.Regions[“MainMenu”];


   menuRegion.Add(new MainMenu());


}


Тут ми використовували ще одну спеціалізовану реалізацію шаблону IoC, а саме шаблон Service Locator. Запускаємо заново тест, і отримуємо знову червоний колір і повідомлення:


Test method WixProject.Test.WixProjectModuleTest.InitializeTest threw exception:  System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary..   


Так, ситуація прояснюється. Ми помилилися в назві регіону. Якби не тест, ми при запуску програми просто б побачив порожнє вікно, і нам би просто залишалося гадати, у чому справа. Виправляємо назву регіону на MenuRegion, запускаємо тест знову і нарешті бачимо зелений колір.


Запускаємо наш додаток, бачимо вікно програми з меню.


Та, здається, по функціональності ми не сильно просунулися. Насправді ми створили цілком працездатний і головне легко розширюваний макет програми, який ми і будемо наповнювати.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*