Unit testing D3JS的单元测试策略是什么?

Unit testing D3JS的单元测试策略是什么?,unit-testing,d3.js,Unit Testing,D3.js,我对D3JS完全陌生,希望了解D3JS的测试策略 对问题的详细说明-我有一个简单的页面,它显示了一个使用TSV文件的线图。 Java脚本代码: function LineManager() {} function LineProperties() { // Line Properties } LineManager.prototype.draw = function(properties) { // D3 code to draw a line with the given propert

我对D3JS完全陌生,希望了解D3JS的测试策略

对问题的详细说明-我有一个简单的页面,它显示了一个使用TSV文件的线图。

Java脚本代码:

function LineManager() {}
function LineProperties() { // Line Properties }
LineManager.prototype.draw = function(properties) { 
  // D3 code to draw a line with the given properties.
}
我想不出编写单元测试时要考虑的测试用例。这是我写的一个样本测试

it("should throw an exception if line graph properties are not set.", function() {
        expect(lineManager.draw.bind(lineManager)).toThrow("Line Graph properties not set");
    });

it("It should have single line chart", function() {
    lineManager.draw(properties);
    expect(lineManager.countLines()).toEqual(1);
});
我已经编写了单元测试,以确保TSV文件正确生成。但是编写一个单元测试来查看数据是否正确呈现有意义吗?对于我的函数来说,这不是更像是一个d3js单元测试而不是单元测试吗


所以我的问题是-对于d3js生成的图表,应该考虑哪些测试?

测试策略

我最终用来测试d3.js代码的策略是创建助手函数来管理我的数据和设置。然后对这些函数进行单元测试。所以对于图表,我会检查每个处理数据的功能,每个设置宽度的功能,图例等等

关于绘图函数,它可能会变得更加棘手,但使用buster.js等测试框架,实现这些功能也非常容易。测试图表的一个好方法是计算页面中的条/行数,检查是否打印图例等

我不会试图检查图表在视觉上是否相同,因为视觉上检查最终结果是否相同是最容易的。但是,在编写绘图函数时,应该非常注意更新时会发生什么(更改数据会绘制两倍的行吗?选择器正确吗?…)


Javascript测试

关于javascript测试的一本好书是:。它提供了大量的示例和策略来测试javascript代码。其中大部分可以直接应用于d3.js代码


工具

function CircleManager() {};

CircleManager.prototype.draw = function(radius) {
    var svg = d3.select("body")
            .append("svg");

    svg.attr("width", 100)
            .attr("height", 100);    

    var circle = svg.append("circle");
    circle.style("stroke", "black")
        .style("fill", "white")
        .attr("r", radius)
        .attr("cx", 50)
        .attr("cy", 50);
};
describe("draw", function() {
    it("Constructs an svg", function() {
        var d3SpyObject = jasmine.createSpyObj(d3, ['append', 'attr']);

        // Returns d3SpyObject when d3.select method is called
        spyOn(d3, 'select').andReturn(d3SpyObject);

        var svgSpyObject = jasmine.createSpyObj('svg', ['append', 'attr', 'style']);

        // Returns svgSpyObject when d3.select.append is called.
        d3SpyObject.append.andReturn(svgSpyObject);


        d3SpyObject.attr.andCallFake(function(key, value) {
            return this;
        });

        svgSpyObject.append.andReturn(svgSpyObject);

        svgSpyObject.attr.andCallFake(function(key, value) {
            return this;
        });

        svgSpyObject.style.andCallFake(function(key, value) {
            return this;
        });

        var circleManager = new CircleManager();
        circleManager.draw(50);
        expect(d3.select).toHaveBeenCalledWith('body');
        expect(d3SpyObject.append).toHaveBeenCalledWith('svg');
        expect(svgSpyObject.attr).toHaveBeenCalledWith('r', 50);
        expect(svgSpyObject.attr).toHaveBeenCalledWith('width', 100);
        expect(svgSpyObject.attr).toHaveBeenCalledWith('height', 100);
        expect(svgSpyObject.style).toHaveBeenCalledWith('stroke', 'black');
        expect(svgSpyObject.style).toHaveBeenCalledWith('fill', 'white');
    });
});
我最近寻找了d3.js代码单元测试的解决方案,最后使用了以下工具:

buster.js

是一个非常完整的框架,用于在多个浏览器中对javascript代码进行单元测试

phantom.js

是一个无头WebKit,可使用JavaScript API编写脚本

这意味着它可以轻松地在javascript上运行自动化测试,而无需使用chrome、safari等浏览器



编辑:我现在会使用jasmine进行单元测试,而selenium(可能通过Saucelabs服务)进行端到端测试

我想我已经找到了自己问题的答案。我会在这里解释的

使用D3JS编写的JS函数无法验证图形是否正确绘制。为此,我们可能必须使用Phantom.js或Chrisopher提到的类似框架。我并不担心D3JS是否正确地绘制了图形,因为它是D3JS的功能,我的代码可以安全地假设D3JS正在工作

我更担心的是传递给D3JS的数据是否正确,是否符合我的要求。通过创建Spy对象,可以确保正确设置图形的属性。我提供了一个样本单元测试,涵盖了使用D3JS绘制圆的JS代码的测试用例

CircleManager.js

function CircleManager() {};

CircleManager.prototype.draw = function(radius) {
    var svg = d3.select("body")
            .append("svg");

    svg.attr("width", 100)
            .attr("height", 100);    

    var circle = svg.append("circle");
    circle.style("stroke", "black")
        .style("fill", "white")
        .attr("r", radius)
        .attr("cx", 50)
        .attr("cy", 50);
};
describe("draw", function() {
    it("Constructs an svg", function() {
        var d3SpyObject = jasmine.createSpyObj(d3, ['append', 'attr']);

        // Returns d3SpyObject when d3.select method is called
        spyOn(d3, 'select').andReturn(d3SpyObject);

        var svgSpyObject = jasmine.createSpyObj('svg', ['append', 'attr', 'style']);

        // Returns svgSpyObject when d3.select.append is called.
        d3SpyObject.append.andReturn(svgSpyObject);


        d3SpyObject.attr.andCallFake(function(key, value) {
            return this;
        });

        svgSpyObject.append.andReturn(svgSpyObject);

        svgSpyObject.attr.andCallFake(function(key, value) {
            return this;
        });

        svgSpyObject.style.andCallFake(function(key, value) {
            return this;
        });

        var circleManager = new CircleManager();
        circleManager.draw(50);
        expect(d3.select).toHaveBeenCalledWith('body');
        expect(d3SpyObject.append).toHaveBeenCalledWith('svg');
        expect(svgSpyObject.attr).toHaveBeenCalledWith('r', 50);
        expect(svgSpyObject.attr).toHaveBeenCalledWith('width', 100);
        expect(svgSpyObject.attr).toHaveBeenCalledWith('height', 100);
        expect(svgSpyObject.style).toHaveBeenCalledWith('stroke', 'black');
        expect(svgSpyObject.style).toHaveBeenCalledWith('fill', 'white');
    });
});
CircleManagerSpec.js

function CircleManager() {};

CircleManager.prototype.draw = function(radius) {
    var svg = d3.select("body")
            .append("svg");

    svg.attr("width", 100)
            .attr("height", 100);    

    var circle = svg.append("circle");
    circle.style("stroke", "black")
        .style("fill", "white")
        .attr("r", radius)
        .attr("cx", 50)
        .attr("cy", 50);
};
describe("draw", function() {
    it("Constructs an svg", function() {
        var d3SpyObject = jasmine.createSpyObj(d3, ['append', 'attr']);

        // Returns d3SpyObject when d3.select method is called
        spyOn(d3, 'select').andReturn(d3SpyObject);

        var svgSpyObject = jasmine.createSpyObj('svg', ['append', 'attr', 'style']);

        // Returns svgSpyObject when d3.select.append is called.
        d3SpyObject.append.andReturn(svgSpyObject);


        d3SpyObject.attr.andCallFake(function(key, value) {
            return this;
        });

        svgSpyObject.append.andReturn(svgSpyObject);

        svgSpyObject.attr.andCallFake(function(key, value) {
            return this;
        });

        svgSpyObject.style.andCallFake(function(key, value) {
            return this;
        });

        var circleManager = new CircleManager();
        circleManager.draw(50);
        expect(d3.select).toHaveBeenCalledWith('body');
        expect(d3SpyObject.append).toHaveBeenCalledWith('svg');
        expect(svgSpyObject.attr).toHaveBeenCalledWith('r', 50);
        expect(svgSpyObject.attr).toHaveBeenCalledWith('width', 100);
        expect(svgSpyObject.attr).toHaveBeenCalledWith('height', 100);
        expect(svgSpyObject.style).toHaveBeenCalledWith('stroke', 'black');
        expect(svgSpyObject.style).toHaveBeenCalledWith('fill', 'white');
    });
});

希望这有帮助。

< P>我想你应该考虑一下:

这似乎真的有道理。我读过其他人的答案,但我有点不同意。我认为我们不仅可以检查是否调用了正确的函数,还可以检查更多。在单元测试级别,只检查一些函数调用很好,但还不够。开发人员编写的此类测试用例将基于开发人员对这些函数是否被调用的理解。但是不管这些方法是否应该被调用,这件事只能在另一个层次上进行检查,因为与其他工作不同,这里的代码使某些东西不返回可以检查的东西,并确保一切都是正确的

我们显然不需要检查D3是否正确工作。所以我们可以在测试代码中使用D3。但是D3呈现SVG,我们可以检查SVG是否在预期的地方有元素。同样,它不会测试SVG是否正确显示和渲染。我们将检查SVG是否具有预期的元素,并将它们设置为预期的元素

例如: 如果这是条形图,我们可以检查条形图的数量。如上面链接中的示例所示,请检查

    // extend beforeEach to load the correct data...
beforeEach(function() {
    var testData =  [{ date: '2014-01', value: 100}, { date: '2014-02', value: 140}, {date: '2014-03', value: 215}];
    c = barChart();
    c.setData(testData);
    c.render();
});

describe('create bars' ,function() {
    it('should render the correct number of bars', function() {
        expect(getBars().length).toBe(3);
    });

    it('should render the bars with correct height', function() {
        expect(d3.select(getBars()[0]).attr('height')).toBeCloseTo(420);
    });

    it('should render the bars with correct x', function() {
        expect(d3.select(getBars()[0]).attr('x')).toBeCloseTo(9);
    });

    it('should render the bars with correct y', function() {
        expect(d3.select(getBars()[0]).attr('y')).toBeCloseTo(0);
    });
});

// added a simple helper method for finding the bars..
function getBars() {
    return d3.selectAll('rect.bar')[0];
}
有些人可能会说我们将在测试代码中使用D3?我们应该再次记住,这里编写测试的目的不是测试D3,而是测试根据代码编译的逻辑和SVG代码

这只是一种方式,jasmine可以帮助我们编写测试,你们也可以在不同的场景中进行更详细的讨论。您可以创建域并检查数据点的宽度和高度,以交叉检查它们是否生成要渲染的数据

我想我很清楚如果没有,请检查此链接:

这篇文章的作者在这里详细解释了如何使用茉莉花。
而且我认为我仍然在深入细节。如果在不同的js功能级别上只需要单元测试,那么可以在不深入元素细节的情况下测试更多的东西。

可能值得一提的是Jest快照测试。Jest/Snapshot对于ReactUI组件很流行,因为它们也很难测试。使用D3可以生成SVG,拍摄快照,并在代码库发展过程中验证是否继续生成相同的输出

function makeChart(id, dataset) {
  const chart = d3.select(id)
      .append("svg")
      .selectAll("circle")
      .data(dataset)
      .enter().append("circle")
      .style("stroke", "gray")
      .style("fill", "black")
      .attr("r", 40)
      .attr("cx", 50)
      .attr("cy", 20);
  return chart;
}

it('renders correctly', () => {
  const myChart = makeChart('#example', [1,2,3])
  expect(myChart.node()).toMatchSnapshot();
});
此测试未经测试

快照可以是字符串
“Foo-Bar”
JSON对象
{Foo:Bar}
等的任何输出

基本上,您必须运行一次包含
.toMatchSnapshot
的测试。开玩笑就行了