javascript HTML Canvas 单元测试
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 
原文地址: http://stackoverflow.com/questions/4406864/
Warning: these are provided under cc-by-sa 4.0 license.  You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
HTML Canvas Unit testing
提问by pcjuzer
How can I unit-test Javascript that draws on an HTML canvas? Drawing on the canvas should be checked.
如何对在 HTML 画布上绘制的 Javascript 进行单元测试?应检查画布上的绘图。
采纳答案by Juho Veps?l?inen
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. The following example (RightJS code) shows one way to do this:
正如问题评论中所讨论的,检查是否已使用合适的参数调用了某些函数非常重要。pcjuzer 提出了使用代理模式。以下示例(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. You might need to hack it a bit to fit your purposes. Good luck!
我使用了类似的模式来实现 API 中缺少的一些功能。您可能需要稍微修改一下以符合您的目的。祝你好运!
回答by cesutherland
I wrote an example for unit-testing canvas and other image-y types with Jasmine and js-imagediff.
我编写了一个示例,用于使用 Jasmine 和 js-imagediff 对画布和其他图像类型进行单元测试。
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. 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.
我发现这比确保已调用模拟 Canvas 上的特定方法更好,因为不同系列的方法可能会产生相同的方法。通常,我会创建一个具有预期值的画布,或者使用代码的已知稳定版本来测试开发版本。
回答by Sam
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. I wrote it in ec2015.
我制作非常简单的画布并用摩卡咖啡测试它们。我的做法与 Juho Veps?l?inen 类似,但我的看起来更简单一些。我是在 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
在这里阅读模拟
回答by Richard
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). This new version can then be used for future comparisons.
并且可能有一个按钮,假设输出正常,则更新服务器上的图像版本(通过将 toDataUrl() 输出发送给它)。然后可以将这个新版本用于未来的比较。
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.
左边的图表是真正的画布,而右边是存储在数据库中的图像,它应该是什么样子的(从我知道代码正在运行时获取)。将有很多这些来测试我的代码的所有(最终)方面。
回答by Cristi Ingineru
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. Here are a few options:
最好拦截对画布对象的调用并进行调查。这里有几个选项:
- Create a wrapper object that records all the calls. Juho Veps?l?inen posted a such example.
 - If possible use a library like frabric.js that offers a higher level of abstraction for drawing. The "drawings" are JS objects that can be inspected directly or converted to SVG which is easier to inspect and test.
 - Use Canteento intercept all the function calls and attribute changes of a canvas object. This is similar with option 1.
 - Use Canteen with rabbitwhich 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.
 
回答by JT703
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. It's using alot of the putImageData/getImageData. This examplemight help with what you are trying to do with a test.
现在,我还没有尝试过这个,但它可能更符合你的需求。Cake 是画布的库。它使用了大量的 putImageData/getImageData。 此示例可能有助于您尝试使用测试执行的操作。
Hope that helps answer your question.
希望能帮助回答你的问题。

