CamlJs
На всякий случай, для тех кто возможно не в курсе, суть этого проекта - удобное формирование XML Caml-запросов для SP.CamlQuery и/или для SPServices, с интеллисенсом и подсказками. Выглядит это примерно так:
Особенность проекта в том, что он действительно не только бережет от опечаток, но практически основной целью при создании CamlJS являлось создание качественного интеллисенса и подсказок, чтобы не требовалось быть экспертом в Caml Query чтобы написать средний запрос.
Проект, даже на самой ранней стадии, представлял довольно внушительную гору JavaScript, довольно тяжеловесную в поддержке. В общем, переход на TypeScript был очень логичным шагом и значительно облегчил поддержку проекта и его дальнейшую разработку. Собственно благодаря переходу на TypeScript какая-то дальнейшая работа и стала возможной - сейчас как раз веду работы по новому синтаксису и готовлюсь релизить... :)
При всём этом, проект практически идеален для юнит-тестов: XML-запросы, которые должен генерировать билдер - это просто строки, которые очень легко проверить. Сначала я проводил тестирование "кустарно" - написал файлик и напулял туда простых сравнений. Конечно же со временем этого стало не хватать и проект дозрел до введения уже нормальных юнит-тестов.
tsUnit
Простенький движок для юнит-тестов на TypeScript существует - это tsUnit. Пользоваться примерно так: наследуемся от класса tsUnit.TestClass, используем методы класса-родителя для assert'ов, и плюс потребуется простенький код инициализации.
Пример использования (с сайта проекта):
/// <reference path="tsUnit.ts" /> /// <reference path="Calculations.ts" /> class SimpleMathTests extends tsUnit.TestClass { private target = new Calculations.SimpleMath(); addTwoNumbersWith1And2Expect3() { var result = this.target.addTwoNumbers(1, 2); this.areIdentical(3, result); } addTwoNumbersWith3And2Expect5() { var result = this.target.addTwoNumbers(3, 2); this.areIdentical(4, result); // Deliberate error } } // new instance of tsUnit var test = new tsUnit.Test(); // add your test class (you can call this multiple times) test.addTestClass(new CalculationsTests.SimpleMathTests()); // Use the built in results display test.showResults(document.getElementById('results'), test.run());
Неудобства
Однако мне хотелось заточить движок под CamlJs так, чтобы с тестами было работать проще, и в случае проваленного теста было бы проще найти ошибку. Я видел следующие 2 основных неудобства при работе с тестами:
Во-первых, CamlJs генерирует неформатированный XML, и как следствие строка-образец тоже должна быть неформатированной. В итоге в тесты приходилось запихивать километровые строки с XML, что выглядело очень некрасиво и совершенно нечитабельно.
Во-вторых, в случае больших комплексных запросов с кучей вложенных скобочных выражений, когда тест падал, чрезвычайно сложно было найти, в чем же проблема. Например, попробуйте выяснить, что именно здесь не так :)
Идеальным решением в этом случае была бы, безусловно, подсветка отличий (diff).
Давайте рассмотрим, как я решил эти проблемы.
Сравнение форматированного и неформатированного XML в JavaScript
После нескольких попыток, я предпочел решить проблему хранения XML-простыней в коде тестов путем перегонки обоих кусков XML в объекты и затем в JSON через JSON.stringify и сравнения уже JSON-строк. Таким образом, я смогу хранить XML-фрагменты в коде тестов в форматированном виде, что улучшит читабельность кода.
Когда-то давно я писал про то, что в SharePoint есть встроенная функция для превращения XML-строки в dom element. Эта функция прекрасно работает, однако оказалось, что получившийся dom-объект нельзя сериализовать через JSON.stringify (вообще никакие dom-элементы через JSON.stringify не выводятся)! Готового решения этой проблемы я не нашел, но быстро написал простенькую функцию на основе фрагмента из интернетов, которая dom-элемент перегоняет в объект JavaScript. Функция эта выглядит следующим образом:
function elementToObject(el) { var o = {}; var i = 0; if (el.attributes) { for (i; i < el.attributes.length; i++) { o[el.attributes[i].name] = el.attributes[i].value; } } var children = el.childNodes; if (children.length) { i = 0; for (i; i < children.length; i++) { if (children[i].nodeName == '#text') o['#text'] = children[i].nodeValue; else o[children[i].nodeName] = elementToObject(children[i]); } } return o; }
Дальше элементарно:
var domElement = CUI.NativeUtility.createXMLDocFromString("<root>" + xml + "</root>"); var obj = elementToObject(domElement); return JSON.stringify(obj.root, undefined, 2);
В результате XML из скриншота выше преобразуется вот в такой кусок более ёмкого и читабельного JSON'а:
{ "Where": { "And": { "In": { "FieldRef": { "Name": "Category" }, "Values": { "Value": { "Type": "Integer", "#text": "10" } } }, "Leq": { "FieldRef": { "Name": "ExpirationDate" }, "Value": { "Type": "Date", "Now": {} } } } }, "OrderBy": { "FieldRef": { "Name": "ExpirationDate", "Ascending": "False" } } }
Обратите внимание: неважно, что этот JSON не идеальный. Нет задачи сделать идеальный JSON, есть задача сравнить 2 XML'а.
Итоговый код моих юнит-тестов для проекта CamlJs (включая хелпер для реализации преобразования XML->Json) можно найти на Codeplex.
Возвращаясь к неудобствам: даже имея результаты выполнения в форматированном JSON, сравнение запросов в случае проваленного теста по прежнему являлось серьезной проблемой, т.к. запросы большие и приходилось постоянно скроллить туда-сюда, пытаясь найти отличия. Так что давайте теперь рассмотрим, как я сделал diff :)
Визуальный Diff на JavaScript
На самом деле, тут всё банально, есть готовая библиотека, которая делает diff на js. Мне пришлось только немного пропатчить tsUnit, чтобы обеспечить интеграцию. В итоге получилась вот такая картинка:Сразу видно, что происходит, удобно, работа ускорилась в разы.
Библиотека не идеальная и ищет отличия в словах (т.е. если у вас текст без пробелов, толку от неё мало). Лично мне хватило, т.к. я использую форматирование JSON и как следствие, везде где надо пробелы стоят. Однако есть более крутые библиотеки, и их довольно много. В частности, есть очень хорошая либа google-diff-match-patch от Neil Fraser, я на неё поздновато наткнулся, но алгоритм там более подходящий для моих целей, и библиотека сама очень развитая.
Выводы
Если подумать серьезно о том, что я делал выше, можно сказать следующее:Скорость и качество разработки непосредственно зависят от того, насколько хорошо вы подогнали под себя свою рабочую среду. Если вы постоянно будете терять много времени на выполнении какой-то пусть даже несложной второстепенной задачи, внимание начнет рассеиваться, а эффективность - падать. Так что заботьтесь о том, чтобы работать было удобно.
С другой стороны, очень важно здесь не переусердствовать. Нельзя писать систему чтобы писать систему :) Есть простое решение проблемы - отлично, применяем быстро, и продолжаем работать над основным проектом. Быстро не получилось? Значит без этого "удобства" можно обойтись.
Так что, удобной разработки, и до скорых встреч!
ты его, кстати, в nuget не публикуешь?
ОтветитьУдалитьщас нет вроде, но в готовящемся релизе 2.0 это будет обязательно
Удалитьждемс :)
УдалитьОфигенный пост, спасибо, Андрей!
ОтветитьУдалить