【问题标题】:HTML Canvas Unit testingHTML Canvas 单元测试
【发布时间】:2010-12-10 08:15:04
【问题描述】:

如何对在 HTML 画布上绘制的 Javascript 进行单元测试?应检查画布上的绘图。

【问题讨论】:

  • 你能扩展一下你的问题吗?你到底想测试什么?您可能必须编写某种形式的验证函数来简化测试。
  • 我想测试一个 Javascript 图表绘制引擎。我想检查是否将合适的线条和形状放入 HTML 画布的正确位置。 (坐标、颜色、厚度等)
  • 好的。我想你可以通过 dev.w3.org/html5/2dcontext/#dom-context-2d-getimagedata 来做点什么。您可以使用该方法检查像素的颜色。如果您可以编写一些虚拟测试来确定您将需要哪种 API,这可能会有所帮助。
  • 我完全可以检查是否已在画布上使用合适的参数调用了某些函数。我在考虑某种代理。
  • 一个画布测试工具:github.com/HumbleSoftware/js-imagediff

标签: javascript unit-testing html canvas


【解决方案1】:

我用 Jasmine 和 js-imagediff 编写了一个单元测试画布和其他 image-y 类型的示例。

Jasmine Canvas Unit Testing

我发现这比确保调用模拟 Canvas 上的特定方法要好,因为不同系列的方法可能会产生相同的方法。通常,我会创建一个具有预期值的画布,或者使用已知稳定版本的代码来测试开发版本。

【讨论】:

    【解决方案2】:

    正如问题 cmets 中所讨论的,检查某些函数是否已使用合适的参数调用非常重要。 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);
    

    你可以找到一个功能演示here

    我使用了类似的模式来实现 API 中缺少的一些功能。您可能需要稍微修改一下以适应您的目的。祝你好运!

    【讨论】:

    • 嘿,我看到你在推特上发了这个,想知道你为什么写这样的东西:)
    【解决方案3】:

    我制作了非常简单的画布并用摩卡咖啡测试它们。我的做法与 Juho Vepsäläinen 类似,但我的看起来更简单一些。我在ec2015上写的。

    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 类:

    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]')
      }
    }
    

    一些评估 mock 本身功能的 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]')
        })
    })
    

    评估返回画布的函数的功能的 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]' ])
    })
    

    所以在本例中,myfunction 将您传入的画布作为参数 ( Myfunction(new CanvasMock(500,600), 8, 8) ) 并在其上写入从 0,0 到任何值的行您作为参数( Myfunction(new CanvasMock(500,600),** 8, 8**) )传入,然后返回编辑后的画布。

    因此,当您在现实生活中使用该函数时,您可以传入一个实际的画布,而不是一个画布模拟,然后它会运行相同的方法,但会执行实际的画布操作。

    了解模拟 here

    【讨论】:

      【解决方案4】:

      由于画布上绘制的“形状”和“线条”不是实际对象(就像纸上的墨水),因此很难(不可能?)对其进行正常的单元测试。

      使用标准画布可以做的最好的事情是分析像素数据(来自 putImageData/getImageData。就像 bedraw 所说的那样)。

      现在,我还没有尝试过,但它可能更符合您的需要。 Cake 是一个画布库。它使用了很多 putImageData/getImageData。 This example 可能会帮助您完成测试。

      希望这有助于回答您的问题。

      【讨论】:

      • 虽然繁琐,但可以编写一个模拟 Context 对象,供绘图代码依赖并测试是否对 Context 对象进行了正确调用。
      【解决方案5】:

      我最近一直在研究画布测试,现在我正在考虑一个页面,该页面允许将画布与画布外观的“已知良好”图像版本进行比较。这样可以快速轻松地进行视觉比较。

      也许有一个按钮,假设输出正常,更新服务器上的图像版本(通过发送 toDataUrl() 输出给它)。这个新版本可以用于以后的比较。

      不完全(完全)自动化 - 但它确实使比较代码的输出变得容易。

      编辑:

      现在我做了这个:

      左图是真正的画布,而右图是存储在数据库中的图像,它应该是什么样子(取自我知道代码正在运行时)。会有很多这些来测试我的代码的所有(最终)方面。

      【讨论】:

        【解决方案6】:

        从开发人员的角度来看,画布几乎是只写的,因为一旦绘制,就很难以编程方式取回有用的东西。当然可以逐点识别,但这太乏味了,而且这样的测试很难编写和维护。

        最好拦截对画布对象的调用并进行调查。这里有几个选项:

        1. 创建一个记录所有调用的包装对象。 Juho Vepsäläinen 发布了一个这样的例子。
        2. 如果可能,请使用像 frabric.js 这样的库,它为绘图提供更高级别的抽象。 “图纸”是可以直接检查或转换为更易于检查和测试的 SVG 的 JS 对象。
        3. 使用Canteen拦截一个canvas对象的所有函数调用和属性变化。这与选项 1 类似。
        4. 将 Canteen 与 rabbit 一起使用,它为您提供了一些 Jasmine 自定义匹配器来确定大小和对齐方式,以及一个函数 getBBox(),可用于确定在画布上绘制的内容的大小和位置。

        【讨论】:

          猜你喜欢
          • 2011-03-26
          • 1970-01-01
          • 1970-01-01
          • 2021-06-20
          • 1970-01-01
          • 2020-09-08
          • 1970-01-01
          • 1970-01-01
          • 2011-12-05
          相关资源
          最近更新 更多