Как выполнить модульное тестирование кода Javascript, взаимодействующего с элементами DOM

Задний план:

Я исхожу из фона Java, поэтому не слишком знаком с Javascript.

Мы планируем внедрить модульное тестирование JavaScript как в наш существующий (устаревший) код, так и в будущую работу. В основном мы являемся магазином Java (Spring, Weblogic и т. д.).

Мы рассматриваем варианты, которые дают нам хорошую интеграцию с IDE (идея IntelliJ) и сонар, а также возможность запускать их как часть непрерывной интеграции.

JsTestDriver, кажется, ставит все галочки.

Вопрос:

Большая часть нашего существующего кода javascript а) встроена в JSP и б) использует jQuery для непосредственного взаимодействия с элементами страницы.

Как мы должны протестировать функцию, которая сильно зависит от DOM. Вот несколько примеров кода функций, о которых я говорю:

function enableOccupationDetailsText (){
    $( "#fldOccupation" ).val("Unknown");
    $( "#fldOccupation_Details" ).attr("disabled", "");
    $( "#fldOccupation_Details" ).val("");
    $( "#fldOccupation_Details" ).focus();
}

or

jQuery(document).ready(function(){

    var oTable = $('#policies').dataTable( {
            "sDom" : 'frplitip',
                "bProcessing": true,
                "bServerSide": true,
                "sAjaxSource": "xxxx.do",
                "sPaginationType": "full_numbers",
                "aaSorting": [[ 1, "asc" ]],
                "oLanguage": {
                    "sProcessing":   "Processing...",
                    "sLengthMenu":   "Show _MENU_ policies",
                    "sZeroRecords":  "No matching policies found",
                    "sInfo":         "Showing _START_ to _END_ of _TOTAL_ policies",
                    "sInfoEmpty":    "Showing 0 to 0 of 0 policies",
                    "sInfoFiltered": "(filtered from _MAX_ total policies)",
                    "sInfoPostFix":  "",
                    "sSearch":       "Search:",
                    "sUrl":          "",
                    "oPaginate": {
                        "sFirst":    "First",
                        "sPrevious": "Previous",
                        "sNext":     "Next",
                        "sLast":     "Last"
                }
            },
                "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
                        $('td:eq(0)', nRow).html( "<a href='/ole/policy/general_details.do?policy_id="+aData[0]+"'>"+aData[0]+"</a>" );
                        return nRow;

                },

                "fnServerData" : function ( url, data, callback, settings ) {

                settings.jqXHR = $.ajax( {
                    "url": url,
                    "data": data,
                    "success": function (json) {
                        if (json.errorMessage != null) {
                            var an = settings.aanFeatures.r;
                            an[0].style.fontSize="18px";
                            an[0].style.backgroundColor="red";
                            an[0].style.height="70px";
                            an[0].innerHTML=json.errorMessage;
                            setTimeout('window.location="/xxxx"',1000);
                            //window.location="/xxxxx";
                        } else {
                            $(settings.oInstance).trigger('xhr', settings);
                            callback( json );
                        }
                    },
                    "dataType": "json",
                    "cache": false,
                    "error": function (xhr, error, thrown) {
                        if ( error == "parsererror" ) {
                            alert( "Unexpected error, please contact system administrator. Press OK to be redirected to home page." );
                            window.location="/xxxx";
                        }
                    }
                } );
                }

            } );
        $("#policies_filter :text").attr('id', 'fldKeywordSearch');
        $("#policies_length :input").attr('id', 'fldNumberOfRows');
        $("body").find("span > span").css("border","3px solid red");
        oTable.fnSetFilteringDelay(500);
        oTable.fnSearchHighlighting();
        $("#fldKeywordSearch").focus();

}
);

В последнем случае мой подход будет заключаться в том, что рассматриваемая функция слишком велика и должна быть разбита на более мелкие (единицы), чтобы ее можно было протестировать. Но все точки взаимодействия с DOM, jQuery, таблицами данных, ajax и т. д. усложняют рефакторинг вещей так, как мы это делаем в мире Java, чтобы сделать его более тестируемым.

Таким образом, любые предложения по приведенным выше примерам случаев будут очень признательны!


person Ashkan Aryan    schedule 18.06.2012    source источник
comment
См. также Тестирование манипулирования DOM в тесте Jasmine   -  person Richard JP Le Guen    schedule 18.06.2012
comment
также проверьте безголовый веб-кит phantomjs   -  person obimod    schedule 12.08.2013


Ответы (2)


Чтобы протестировать следующий код:

function enableOccupationDetailsText (){
    $( "#fldOccupation" ).val("Unknown");
    $( "#fldOccupation_Details" ).attr("disabled", "");
    $( "#fldOccupation_Details" ).val("");
    $( "#fldOccupation_Details" ).focus();
}

Вы можете использовать следующий код:

// First, create the elements
$( '<input>', { id: 'fldOccupation' } ).appendTo( document.body );
$( '<input>', { disabled: true, id: 'fldOccupation_Details' } )
    .appendTo( document.body );

// Then, run the function to test
enableOccupationDetailsText();

// And test
assert( $( '#fldOccupation' ).val(), 'Unknown' );
assert( $( '#fldOccupation_Details' ).prop( 'disabled' ), false );

Как видите, это просто классический шаблон setup > run > assert.

person Florian Margaine    schedule 18.06.2012
comment
как насчет удаления этих объектов DOM, чтобы один тест не мешал другому? Будет ли это хорошей практикой? Если да, то как? - person Greg Woods; 31.08.2012
comment
@GregWoods хорошо, вы можете удалить их с помощью чего-то вроде $('#fldOccupation').remove(); $('#fldOccupation_Details').remove(); или просто $( document.body.children ).remove(); - person Florian Margaine; 31.08.2012

возможно, вам пригодится Selenium/SeleniumGrid: http://seleniumhq.org/

Это не "UnitTest" по определению, но вы можете писать тесты селена с java или python (и многими другими...) как модульные тесты. Seleniumtests запускает веб-тесты в реальных браузерах и хорошо подходит для тестирования интерфейсов (и манипуляций с домом).

Изменить: сегодня я наткнулся на этот сайт, описывающий различные способы модульного тестирования, особенно в контексте jQuery: http://addyosmani.com/blog/jquery-testing-tools/

person itinance    schedule 18.06.2012