在单元测试中实现逻辑,以缩短语法并提高测试代码的可重用性

Implementing logic within unit tests to make syntax shorter and test code more re-usable

本文关键字:测试 高测试 代码 实现 单元测试 语法 短语      更新时间:2023-12-29

我经常听到开发人员说测试代码应该是"丑陋的",并且尽可能简单。

原因是,测试中的任何逻辑都需要自己测试,这造成了鸡和蛋的悖论。

我发现通过使用一些简单的逻辑,我的测试变得更加可读、结构化,测试代码也更加可重用。

问题是,这些单元测试有效吗?

我使用karma作为测试运行程序,这个特定的项目使用了一个带有连接资产管理器的节点服务器,以及前端的bower包。

如何使代码更加模块化(AMD?browserfy?),而不必从头开始实现所有内容或引入框架。

目前,全局状态在许多文件和许多闭包中都发生了变化,我不喜欢这样。如何使闭包中的代码仍然是可测试的?也许是这个包裹?

示例代码:

utils.js

function getRealTemplatValues (inputs, templateFn, outerId, innerClass) {
    var i, res;
    for (i = 0; i < inputs.length; i++) {
        res = templateFn(inputs[i]);
        $('#' + outerId).append(res);
    }
    return $('.' + innerClass).map(function(){
        return $(this).html();
    }).get();
};

function assertEqual (done, expect, inputs, outputs, fn, decorator) {
    function iter (input, output, cb) {
        var i;
        for (i = 0; i < inputs.length; i++) {
            cb(input[i], output[i]);
        }
    };
    function cb (input, output) {
        output = !!decorator ? decorator(output) : output;
        expect(fn(input, decorator)).toBe(output);
        done && done();
    }
    iter(inputs, outputs, cb, decorator);
};

helper.scenario.js

describe('helpers', function () {
    var inputs = ["#string#string#string", "string#string", "string#string#string#", "#", "###"],
        outputs = ["#string #string #string", "string #string", "string #string #string#", "#", "###"],
        decorString = 'magic!',
        outerId = "container",
        innerClass = "inner",
        decorator = function(input) {return input + decorString};

   describe('breakHashtags - unit', function(done) {
       it('should break hashtags with prefixed with spaces - non decorated', function (done) {
           return assertEqual(done, expect, inputs, outputs, breakHashtags);
       });
       it('should break hashtags with prefixed with spaces - decorated', function () {
           return assertEqual(done, expect, inputs, outputs, breakHashtags, decorator);
       });
   });
    describe('handle bars integration', function () {
        var outerId = "container",
            outerClass = "inner",
            fixture = '<div id="' + outerId + '"></div>',
            template = Handlebars.compile('<div class="' + outerClass + '">{{breakHashtags hashtags}}</div>'),
            decoratedTemplate = Handlebars.compile('<div id="inner">{{breakHashtags hashtags decoratorHelper}}</div>');
        beforeEach( function () {
            addFixture(fixture);
        });
        afterEach( function () {
            clearMyFixtures();
            Handlebars.helpers['decoratorHelper'] && delete Handlebars.helpers['decoratorHelper'];
        });
        it('should have the breakHashtags function registered as a helper', function () {
            expect(Handlebars.helpers['breakHashtags']).toEqual(breakHashtags);
        });
        it('should replace hashtags with hashtags prefixed with spaces', function(){
            var realValues = getRealTemplatValues(inputs, template, outerId, outerClass);
            assertEqual(done, expect, inputs, realValues, breakHashtags);
        });
        it('should replace hashtags with hashtags prefixed with ' +
            'spaces and invoke the decorator on theo put', function(){
            Handlebars.registerHelper('decoratorHelper', decorator);
            var realValues = getRealTemplatValues(inputs, decoratedTemplate, outerId, outerClass);
            assertEqual(done, expect, inputs, realValues, breakHashtags, decorator);
        });
    });
});

helpers.js:

function breakHashtags (text, decorator) {
    var pattern = /'w(#).+/i,
        p1, p2, idx = text.search(pattern), prefix = '';
    while (idx > 0) {
        if (idx === 1) {
            text = text.substring(idx);
            prefix = text.substring(0, 1);
        }
        else{
            p1 = text.substring(0, idx + 1);
            p2 = text.substring(idx + 1);
            text = p1 + ' ' + p2;
            console.log(p1, p2, text)
        }
        idx = text.search(pattern);
    }
    return !!decorator ? decorator(prefix + text) : prefix + text;
}
Handlebars.registerHelper('breakHashtags', breakHashtags);

我的意见:我觉得你太过分了。例如:

it('should replace hashtags with hashtags prefixed with spaces', function(){
            var realValues = getRealTemplatValues(inputs, template, outerId, outerClass);
            assertEqual(done, expect, inputs, realValues, breakHashtags);
});

它可能对你来说更可读,但只对你来说。编程是一种团队游戏。当别人第一次看到那个代码时,他根本不知道发生了什么。这个测试中有一个描述,但它与你实际测试的内容无关。不知道你的代码到底做了什么,但测试应该是这样的:

it('should replace hashtags with hashtags prefixed with spaces', function(){
            var result = testedFunction("#hashtag1#hashtag2#hashtag3");
            assertThat(result).isEqualTo("#hashTag1 #hashTag2 #hashTag3");
});

现在每个测试都是一个整体。没有人需要检查不同的文件才能理解它。如果有人更改了你的助手功能怎么办?你注意到了吗?

如何使代码更加模块化(AMD?browserfy?),而不需要从头开始实现一切,或者引入一个框架。

框架出了什么问题?毕竟你才刚刚开始写自己的东西。最好使用第三方框架,因为团队中的每个人都知道它的确切作用。当你编写自己的助手时,每个人都必须从头开始学习。此外,最好编写帮助程序,使测试在一般情况下更加强大。可以为参数化测试或通用断言创建简单的框架。它是如何工作的,它做什么,何时以及如何改变都非常清楚。这样你就可以在所有的测试中使用它们

但是当您创建像assertEqual (done, expect, inputs, outputs, fn, decorator)这样的函数时,没有人知道它的作用。为什么assertEquals有这么多参数?它最多应该有2-3条(实际的、预期的、错误消息)。所以我可以在需要的时候更改assertEquals吗?还是我应该因为它对某人很重要而保持原样,然后复制粘贴?这样的测试是的维护噩梦

我经常听到开发人员说测试代码应该是"丑陋的"尽可能简单。

是的,它们应该是朴素的,不,它们不应该是丑陋的。这是你的代码,所以重构它。但重构的方式要使它们成为可读的文档。每一个测试都应该是文档中的一小部分。为每个人,而不仅仅是为你。而无需在其他辅助文件或函数

中查找任何内容