简体   繁体   English

HTML Canvas单元测试

[英]HTML Canvas Unit testing

How can I unit-test Javascript that draws on an HTML canvas? 如何对在HTML画布上绘制的Javascript进行单元测试? Drawing on the canvas should be checked. 应检查画布上的绘图。

I wrote an example for unit-testing canvas and other image-y types with Jasmine and js-imagediff. 我用Jasmine和js-imagediff编写了一个单元测试canvas和其他image-y类型的例子。

Jasmine Canvas Unit Testing 茉莉帆布单元测试

I find this to be better than making sure specific methods on a mock Canvas have been invoked because different series of methods may produce the same method. 我发现这比确保调用模拟Canvas上的特定方法更好,因为不同系列的方法可能会产生相同的方法。 Typically, I will create a canvas with the expected value or use a known-stable version of the code to test a development version against. 通常,我将创建一个具有预期值的画布,或使用已知稳定版本的代码来测试开发版本。

As discussed in the question comments it's important to check that certain functions have been invoked with suitable parameters. 正如问题评论中所讨论的那样,检查某些函数是否已使用合适的参数调用是很重要的。 pcjuzer proposed the usage of proxy pattern. pcjuzer提出了代理模式的使用。 The following example (RightJS code) shows one way to do this: 以下示例(RightJS代码)显示了一种方法:

var Context = new Class({
    initialize: function($canvasElem) {
        this._ctx = $canvasElem._.getContext('2d');

        this._calls = []; // names/args of recorded calls

        this._initMethods();
    },
    _initMethods: function() {
        // define methods to test here
        // no way to introspect so we have to do some extra work :(
        var methods = {
            fill: function() {
                this._ctx.fill();
            },
            lineTo: function(x, y) {
                this._ctx.lineTo(x, y);
            },
            moveTo: function(x, y) {
                this._ctx.moveTo(x, y);
            },
            stroke: function() {
                this._ctx.stroke();
            }
            // and so on
        };

        // attach methods to the class itself
        var scope = this;
        var addMethod = function(name, method) {
            scope[methodName] = function() {
                scope.record(name, arguments);

                method.apply(scope, arguments);
            };
        }

        for(var methodName in methods) {
            var method = methods[methodName];

            addMethod(methodName, method);
        }
    },
    assign: function(k, v) {
        this._ctx[k] = v;
    },
    record: function(methodName, args) {
        this._calls.push({name: methodName, args: args});
    },
    getCalls: function() {
        return this._calls;
    }
    // TODO: expand API as needed
});

// Usage
var ctx = new Context($('myCanvas'));

ctx.moveTo(34, 54);
ctx.lineTo(63, 12);

ctx.assign('strokeStyle', "#FF00FF");
ctx.stroke();

var calls = ctx.getCalls();

console.log(calls);

You can find a functional demo here . 你可以在这里找到一个功能演示。

I have used a similar pattern to implement some features missing from the API. 我使用了类似的模式来实现API中缺少的一些功能。 You might need to hack it a bit to fit your purposes. 您可能需要将其破解以适合您的目的。 Good luck! 祝好运!

I make really simple canvases and test them with mocha. 我制作了非常简单的画布并用摩卡测试它们。 I do it similarly to Juho Vepsäläinen but mine looks a little simpler. 我和JuhoVepsäläinen的做法类似,但我看起来有点简单。 I wrote it in ec2015. 我在ec2015写的。

CanvasMock class: CanvasMock类:

import ContextMock from './ContextMock.js'

export default class {
  constructor (width, height)
  {
    this.mock = [];
    this.width = width;
    this.height = height;
    this.context = new ContextMock(this.mock);
  }

  getContext (string)
  {
    this.mock.push('[getContext ' + string + ']')
    return this.context
  }
}

ContextMock class: ContextMock类:

export default class {
  constructor(mock)
  {
    this.mock = mock
  }

  beginPath()
  {
    this.mock.push('[beginPath]')
  }

  moveTo(x, y)
  {
    this.mock.push('[moveTo ' + x + ', ' + y + ']')
  }

  lineTo(x, y)
  {
    this.mock.push('[lineTo ' + x + ', ' + y + ']')
  }

  stroke()
  {
    this.mock.push('[stroke]')
  }
}

some mocha tests that evaluates the functionality of the mock itself: 一些mocha测试,用于评估模拟本身的功能:

describe('CanvasMock and ContextMock', ()=> {
    it('should be able to return width and height', ()=> {
      let canvas = new CanvasMock(500,600)
      assert.equal(canvas.width, 500)
      assert.equal(canvas.height, 600)
    })
    it('should be able to update mock for getContext', ()=> {
      let canvas = new CanvasMock(500,600)
      let ctx = canvas.getContext('2d')
      assert.equal(canvas.mock, '[getContext 2d]')
    })
})

A mocha tests that evaluates the functionality of a function that returns a canvas: 一个mocha测试,用于评估返回画布的函数的功能:

import Myfunction from 'MyFunction.js'

describe('MyFuntion', ()=> {
it('should be able to return correct canvas', ()=> {
  let testCanvas = new CanvasMock(500,600)
  let ctx = testCanvas.getContext('2d')
  ctx.beginPath()
  ctx.moveTo(0,0)
  ctx.lineTo(8,8)
  ctx.stroke()
  assert.deepEqual(MyFunction(new CanvasMock(500,600), 8, 8), canvas.mock, [ '[getContext 2d]', '[beginPath]', '[moveTo 0, 0]', [lineTo 8, 8]', '[stroke]' ])
})

so in this example myfunction takes the canvas you passed in as an argument ( Myfunction( new CanvasMock(500,600) , 8, 8) ) and writes a line on it from 0,0 to whatever you pass in as the arguments ( Myfunction(new CanvasMock(500,600),** 8, 8**) ) and then returns the edited canvas. 所以在这个例子中myfunction将你传入的画布作为参数(Myfunction( new CanvasMock(500,600) ,8,8))并在其上写一行从0,0到你传入的任何参数(Myfunction(new) CanvasMock(500,600),** 8,8 **))然后返回编辑过的画布。

so when you use the function in real life you can pass in an actual canvas, not a canvas mock and then it will run those same methods but do actual canvas things. 因此,当您在现实生活中使用该函数时,您可以传入一个实际的画布,而不是画布模拟,然后它将运行相同的方法但执行实际的画布。

read about mocks here 在这里阅读嘲笑

I've been looking at canvas testing recently and I've now thought about a page that allows comparing the canvas to a "known good" image version of what the canvas should look like. 我最近一直在看画布测试,现在我想到了一个页面,它允许将画布与画布应该看起来的“已知良好”图像版本进行比较。 This would make a visual comparison quick and easy. 这将使视觉比较快速简便。

And maybe have a button that, assuming the output is OK, updates the image version on the server (by sending the toDataUrl() output to it). 并且可能有一个按钮,假设输出正常,更新服务器上的图像版本(通过向其发送toDataUrl()输出)。 This new version can then be used for future comparisons. 然后可以将此新版本用于将来的比较。

Not exactly (at all) automated - but it does make comparing the output of your code easy. 并不完全(根本没有)自动化 - 但它确实可以轻松地比较代码的输出。

Edit: 编辑:

Now I've made this: 现在我做了这个:

实用程序来测试画布输出

The left chart is the real canvas whilst the right is an image stored in a database of what it should look like (taken from when I know the code is working). 左边的图表是真正的画布,右边是存储在数据库中的图像,它应该是什么样子(从我知道代码工作时开始)。 There'll be lots of these to test all (eventually) aspects of my code. 将有很多这些来测试我的代码的所有(最终)方面。

From a developer's point of view the canvas is almost write-only because once drawn it's difficult to programmatically get something useful back. 从开发人员的角度来看,画布几乎是只写的,因为一旦绘制它就很难以编程方式获得有用的东西。 Sure one can do a point by point recognition but that's too tedious and such tests are hard to be written and maintained. 当然可以逐点识别,但这太繁琐了,这样的测试很难编写和维护。

It's better to intercept the calls made to a canvas object and investigate those. 拦截对canvas对象的调用并调查它们会更好。 Here are a few options: 以下是一些选项:

  1. Create a wrapper object that records all the calls. 创建一个记录所有调用的包装器对象。 Juho Vepsäläinen posted a such example. JuhoVepsäläinen发布了一个这样的例子。
  2. If possible use a library like frabric.js that offers a higher level of abstraction for drawing. 如果可能的话,使用像frabric.js这样的库,它为绘图提供了更高级别的抽象。 The "drawings" are JS objects that can be inspected directly or converted to SVG which is easier to inspect and test. “图纸”是JS对象,可以直接检查或转换为SVG,更容易检查和测试。
  3. Use Canteen to intercept all the function calls and attribute changes of a canvas object. 使用Canteen拦截画布对象的所有函数调用和属性更改。 This is similar with option 1. 这与选项1类似。
  4. Use Canteen with rabbit which offers you a few Jasmine custom matchers for size and alignment and a function getBBox() that can be used to determine the size and the position of the stuff being drawn on the canvas. 使用Canteen with rabbit ,它为您提供了一些用于大小和对齐的Jasmine自定义匹配器以及一个函数getBBox(),可用于确定在画布上绘制的东西的大小和位置。

Since the "shapes" and "lines" drawn on a canvas are not actual objects (it's like ink on paper), it would be very hard (impossible?) to do a normal unit test on that. 由于在画布上绘制的“形状”和“线条”不是实际的对象(就像纸上的墨水一样),因此对它进行正常的单元测试将非常困难(不可能?)。

The best you can do with standard canvas it analyze the pixel data (from the putImageData/getImageData. Like what bedraw was saying). 使用标准画布可以做的最好的事情就是分析像素数据(来自putImageData / getImageData。就像bedraw所说的那样)。

Now, I haven't tried this yet, but it might be more what you need. 现在,我还没有尝试过,但它可能更符合您的需求。 Cake is a library for the canvas. Cake是画布的库。 It's using alot of the putImageData/getImageData. 它使用了很多putImageData / getImageData。 This example might help with what you are trying to do with a test. 此示例可能有助于您尝试使用测试执行的操作。

Hope that helps answer your question. 希望有助于回答您的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM