Share via


Execução de teste

Linhas curvas para o Bing Maps AJAX

James McCaffrey

Baixar o código de exemplo

James McCaffreyNa coluna deste mês, apresento uma função de JavaScript que pode ser usada para adicionar linhas curvas ao controle de mapa do Bing Maps AJAX e explico os princípios usados para testar a função.

A melhor maneira de você saber do que estou falando é conferir a Figura 1. Ela mostra um controle de mapa do Bing Maps AJAX da área de Denver, no centro-oeste dos EUA. A função de JavaScript personalizada, que este artigo explica, está sendo usada para desenhar a linha curva azul partindo da cidade de Boulder — o local da Universidade do Colorado (que uma de minhas filhas frequenta) — ao Aeroporto Internacional de Denver (do qual entro e saio muito!). A função personalizada é chamada AddBezierCurve. Além de ser uma função altamente útil se você já trabalhou com o Bing Maps, a rotina AddBezierCurve oferece uma ótima maneira de demonstrar os princípios de testes de API e contém uma matemática interessante que pode ser útil em outros cenários de programação.

The Custom Function AddBezierCurve Adds a Curved Line to Bing Maps

Figura 1 A função personalizada AddBezierCurve adiciona uma linha curva ao Bing Maps

Nas seções a seguir, primeiramente apresentarei uma breve explicação da criação de uma curva de Bezier, que é a técnica matemática subjacente usada por AddBezierCurve. A seguir, eu o conduzirei pela função linha por linha, de modo que você será capaz de modificar o código-fonte de acordo com suas necessidades, se necessário. Concluirei descrevendo os princípios gerais e as técnicas específicas que você pode usar para testar AddBezierCurve e funções de JavaScript semelhantes. Esse artigo considera que você tem uma familiaridade básica com o JavaScript, mas não considera que você já programou com a biblioteca de controle do Bing Maps AJAX antes. Penso que você considerará os tópicos apresentados aqui interessantes e úteis adições às suas habilidades de desenvolvedor e testador.

Curvas de Bezier

As curvas de Bezier podem ser usadas para desenhar uma linha curva entre dois pontos. Apesar de as curvas de Bezier terem muitas variações, a forma mais simples é chamada de curva de Bezier quadrática e requer duas extremidades, geralmente chamadas de P0 e P2, e um ponto intermediário, P1, que determina a forma da curva. Veja um exemplo no gráfico XY na Figura 2.

Constructing Bezier Curves

Figura 2 Criando curvas de Bezier

Os pontos P0, P1 e P2 são os círculos vermelhos abertos no gráfico. As duas extremidades são P0 = (1,2) e P2 = (13,8). O ponto intermediário é P1 = (7,10). A função Bezier aceita um parâmetro, geralmente chamado de t, que vai de 0,0 a 1,0. Cada valor de t produz um ponto entre P0 e P2 em uma curva.

Na Figura 2, usei cinco valores para t: 0,0, 0,25, 0,50, 0,75 e 1,00. Isso produziu os cinco pontos mostrados como pontos menores no gráfico. Apresentarei as equações usadas quando passar pelo código da função AddBezierCurve. Você pode ver que o primeiro ponto Bezier para t = 0,0 é (1,2) = P0 e que o último ponto Bezier para t = 1,0 é (13,8) = P2. Os valores de t = 0,0 e t = 1,0 gerarão os pontos P0 e P2 em geral. Se conectarmos os pontos de Bezier com os segmentos de linha, teremos uma bonita curva entre P0 e P2. Mais valores de t geram mais pontos, que criam uma trajetória de curva mais suave.

Dadas as extremidades P0 e P2, o valor de P1 determina a forma da curva de Bezier resultante. No exemplo na Figura 2, escolhi arbitrariamente um ponto no meio de P0 e P2 no eixo horizontal e ligeiramente acima do ponto mais alto (P2) no eixo vertical. Se eu inclinasse P1 um pouco para a esquerda, a curva mudaria para a esquerda e se tornaria mais simétrica. Se eu aumentasse a altura de P1, a curva seria mais alta e com um pico maior.

Chamando a função AddBezierCurve

Na Figura 1, você pode ver que meu mapa está em uma página da Web chamada CurveDemo.html (disponível no download do código). A estrutura geral de CurveDemo.html é apresentada na Figura 3.

Figura 3 A estrutura da página da Web de demonstração

    <html>
    <!-- CurveDemo.html -->
    <head>
    <title>Bing Maps AJAX Bezier Curve</title>
    <script type="text/javascript"
     src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.3">
    </script>
    <script type="text/javascript">
    
     var map = null; // global VEMap object
    
     function AddBezierCurve(start, finish, arcHeight, skew, color, width,
      upDown, numSegments)
     {
       // Code here
     }
    
     function MakeMap()
     {
      map = new VEMap('myMap');
      map.LoadMap(new VELatLong(39.8600, -105.0000), 10, VEMapStyle.Road);
      var start = new VELatLong(40.0200, -105.2700); // Boulder
      var finish = new VELatLong(39.9000, -104.7000); // airport
      var arcHeight = 0.20;
      var skew = -0.004;
      var color = new VEColor(0,0,255,1.0); // blue
      var width = 6;
      var numSegments = 200;
    
      AddBezierCurve(start, finish, arcHeight, skew, color, width, 'up', numSegments);
     }
    
    </script>
    </head>               
    <body onload="MakeMap();">
    <div id='myMap' style="position:relative; width:800px; height:600px;"></div>
    </body>
    </html>

Depois da marca HTML <title>, usei o elemento <script> para obter acesso programático à versão 6.3 da biblioteca de controle de mapas do Bing Maps AJAX. Observe que não estou usando boas práticas de codificação, como incluir uma declaração DOCTYPE, para diminuir o tamanho de meu código de demonstração. No momento em que escrevia este artigo, a versão atual da biblioteca era a 7. Ela tem desempenho aprimorado, mas tem uma base de código completamente diferente das versões anteriores. A menos que você esteja usando AddBezierCurve com um conjunto de API do Bing Maps AJAX existente, você provavelmente usará a versão 7. Você deve ser capaz de realocar o código apresentado aqui para o Bing Maps AJAX versão 7 sem problemas.

Eu declarei um objeto VEMap global chamado map e criei uma instância nula. Observe que, devido ao JavaScript não usar declarações de tipo explícitas, sem um comentário não fica óbvio que map tem o tipo VEMap.

A função AddBezierCurve aceita até oito valores de parâmetro. Os primeiros quatro parâmetros (start, finish, arcHeight e skew) são obrigatórios. Os próximos quatro parâmetros (color, width, upDown e numSegments) são opcionais e têm valores padrão. Os parâmetros star e finish são objetos do tipo VELatLong (latitude e longitude) e representam as extremidades P0 e P2 descritas na seção anterior. O parâmetro arcHeight representa a altura do ponto P1 de definição de forma intermediário. O parâmetro skew representa o ajuste esquerda-direita do ponto P1. O parâmetro color é a VEColor da curva. Width é o valor numérico da largura da curva. O parâmetro upDown é uma cadeia de caracteres que pode ser “up” ou “down” e especifica se a curva deve dobrar para cima ou para baixo. O parâmetro numSegments especifica o número de valores a ser usado para o valor t de Bezier que, por sua vez, determina quantos segmentos de linha compõem a curva.

A função MakeMap cria uma instância do objeto VEMap usando a nova palavra-chave e define a ID de controle como “myMap”, de modo que o navegador da Web possa usar a resposta do AJAX para saber onde colocar o controle de mapa em CurveDemo.html. Usei o método LoadMap para posicionar inicialmente o mapa centralizado ao norte de Denver — com um nível de zoom de 10 — usando a visualização Road. A seguir, defini o parâmetro start como a latitude e longitude de Boulder e defini o parâmetro finish como uma área ao norte do Aeroporto Internacional de Denver. Defini arcHeight como 0,20. Como veremos na próxima seção, arcHeight é interpretado como graus de latitude acima do ponto médio entre P0 (start) e P2 (finish). Defini a inclinação como -0,004 para mover ligeiramente a dobra da curva 0,004 graus de longitude à esquerda. Defini a cor como azul sem transparência alfa, a largura da linha curva como 6 e o número de segmentos de linha como 200 e, então, chamei a função AddBezierCurve com uma direção “para cima”.

Na marca do corpo HTML, usei o evento onload para chamar MakeMap que, por sua vez, chama AddBezierCurve.

Definindo a função AddBezierCurve

A função AddBezierCurve começa com:

function AddBezierCurve(start, finish, arcHeight, skew, color, width,
 upDown, numSegments)
{
  // Preconditions and parameter descriptions here
  if (typeof color == 'undefined') { var color = new VEColor(255,0,0,1.0); }
  if (typeof width == 'undefined') { var width = 2; }
  if (typeof upDown == 'undefined') { var upDown = 'up'; }
  if (typeof numSegments == 'undefined') { var numSegments = 10; }
  ...

Para economizar espaço, removi os comentários que descrevem as pré-condições presumidas da função (por exemplo, a existência de uma instância do objeto VEMap global com nome map) e descrições dos oito parâmetros de entrada. O código da função começa pela definição de parâmetros padrão. Usei o operador typeof do JavaScript para determinar se os parâmetros color, width, upDown e numSegments estão presentes ou não. O operador typeof retornará a cadeia de caracteres “undefined”, em vez de nulo, se a variável não estiver presente. Se o parâmetro color estiver ausente, crio um objeto VEColor com escopo local com o nome color e uma instância para vermelho (os parâmetros de VEColor são red, green, blue e transparency). Com a mesma abordagem, criei valores padrão para width (2), upDown (“up”) e numSegments (10).

A função continua:

if (start.Longitude > finish.Longitude) {   
 var temp = start;
 start = finish;
 finish = temp;
}

if (numSegments < 2)
 numSegments = 2;
...

Normalizei os parâmetros start e finish de VELatLong, de modo que o ponto inicial fique à esquerda do ponto final. Tecnicamente, isso não é necessário, mas ajuda a tornar o código mais fácil de entender. Executei uma verificação de erros no parâmetro numSegments para assegurar que haja pelo menos dois segmentos de linha para a curva resultante.

Em seguida, computei as coordenadas de um ponto entre P0 (start) e P2 (finish) na linha que conecta P0 e P2:

var midLat = (finish.Latitude + start.Latitude) / 2.0;
var midLon = (finish.Longitude + start.Longitude) / 2.0;
...

Esse ponto funcionará como o ponto inicial para construir o ponto P1 intermediário usando os parâmetros arcHeight e skew.

Em seguida, determinei P1:

if (Math.abs(start.Longitude - finish.Longitude) < 0.0001) { 
 if (upDown == 'up')
   midLon -= arcHeight;
 else
   midLon += arcHeight;
 midLat += skew;
}
else { // 'normal' case, not vertical
 if (upDown == 'up')
   midLat += arcHeight;
 else
   midLat -= arcHeight;
 midLon += skew;
}
...

Explicarei a primeira parte da lógica do código em breve. A segunda parte da lógica de ramificação é o caso comum em que a linha que conecta start e finish não é vertical. Nesse caso, verifiquei o valor do parâmetro upDown e, se ele for “up”, adiciono o valor arcHeight ao valor de latitude (up-down) da referência base do ponto intermediário e, então, adiciono o valor de inclinação à longitude (left-right) da referência base. Se o parâmetro upDown não for “up”, assumo que upDown é “down” e subtraio arcHeight do componente de latitude up-down da referência base do ponto intermediário. Observe que, em vez de usar um parâmetro upDown explícito, pude eliminar upDown totalmente e apenas inferir que valores positivos de arcHeight significam "para cima" e valores negativos de arcHeight significam "para baixo".

A primeira parte da lógica de ramificação cuida das linhas verticais ou quase verticais. Aqui, efetivamente troquei as funções do valor up-down de arcHeight e inclinei o valor left-right. Observe que não há parâmetro leftRight para a inclinação; valores positivos significam "para a direita" e valores negativos significam "para a esquerda".

Com tudo pronto, inseri o algoritmo de geração da curva de Bezier:

var tDelta = 1.0 / numSegments;

var lons = new Array(); // 'x' values
for (t = 0.0; t <= 1.0; t += tDelta) {
var firstTerm = (1.0 - t) * (1.0 - t) * start.Longitude;
 var secondTerm = 2.0 * (1.0 - t) * t * midLon;
 var thirdTerm = t * t * finish.Longitude;
 var B = firstTerm + secondTerm + thirdTerm;
 lons.push(B);
}
...

Primeiramente, computei a diferença entre os valores de t. Lembre-se da seção anterior em que t vai de 0,0 a 1,0 (inclusive). Portanto, se numSegments = 3, então tDelta deve ser 0,33, e meus valores t devem ser 0,00, 0,33, 0,67 e 1,00, resultando em quatro pontos Bezier e três segmentos de linha. Em seguida, criei uma nova matriz chamada lons para conter os valores x, que são as longitudes. Uma explicação detalhada das equações de Bezier está fora do escopo deste artigo; entretanto, observe que há três termos que dependem do valor de t, P0 (start), P1 (midLon) e P2 (finish). As curvas de Bezier são realmente interessantes e há muita informação sobre elas disponível na Internet.

Em seguida, usei as mesmas equações de Bezier para computar os valores de y (latitude) em uma matriz com o nome lats:

var lats = new Array(); // 'y' values
for (t = 0.0; t <= 1.0; t += tDelta) {
  var firstTerm = (1.0 - t) * (1.0 - t) * start.Latitude;
  var secondTerm = 2.0 * (1.0 - t) * t * midLat;
  var thirdTerm = t * t * finish.Latitude;
  var B = firstTerm + secondTerm + thirdTerm;
  lats.push(B);
}
...

Agora, finalizo com a função AddBezierCurve:

var points = new Array();
 for (i = 0; i < lats.length; ++i) {
  points.push(new VELatLong(lats[i], lons[i]));
 }

 var curve = new VEShape(VEShapeType.Polyline, points);
 curve.HideIcon();
 curve.SetLineColor(color);
 curve.SetLineWidth(width);
 map.AddShape(curve);
}

Criei uma matriz de objetos VELatLong com o nome points e adicionei os pares latitude-longitude das matrizes lats e lons aos pontos da matriz. Depois criei uma instância de VEShape do tipo Polyline, ocultei o incômodo ícone padrão, defini a cor e a largura da Polyline e usei o método AddShape para colocar a curva de Bezier no objeto VEMap global com nome map.

Testando a função AddBezierCurve

Testar a função AddBezierCurve não é simples. A função tem parâmetros de objeto (start, finish e color), parâmetros numéricos (arcHeight, skew, width e numSegments) e um parâmetro de cadeia de caracteres (upDown). Na verdade, alguns de meus colegas usam funções semelhantes como base para perguntas de entrevista para cargos de testador de software. Essa forma de teste é geralmente chamada de teste de API ou teste de módulo. A primeira coisa a verificar é a funcionalidade básica ou, em outras palavras: a função faz o que deve fazer em situações mais ou menos normais? Em seguida, um bom testador começaria a verificar os parâmetros da função e a determinar o que aconteceria no caso de entradas ruins ou problemáticas.

A função AddBezierCurve não executa uma verificação inicial no início e no fim dos valores do parâmetro VELatLong. Se ambos forem nulos ou indefinidos, o mapa será criado, mas nenhuma linha curva aparecerá. Da mesma maneira, a função não verifica valores ilegais no início ou no fim. Os objetos VELatLong usam o Sistema Geodésico Mundial 1984 (WGS 84) de coordenadas, no qual as latitudes legais estão no intervalo [-90.0, +90,0] e as longitudes legais estão no intervalo [-90,0, +90,0]. Valores ilegais pode fazer com que AddBezierCurve produza resultados inesperados e incorretos. Outra possibilidade de entrada ruim para os valores de parâmetro start e finish são objetos do tipo errado — um objeto VEColor, por exemplo.

Testadores experientes também testariam os parâmetros numéricos de arcHeight e skew de forma semelhante. Valores de condições-limite interessantes para esses parâmetros incluem 0,0, -1,0 + 1,0 e 1,7976931348623157e+308 (o número máximo do JavaScript em muitos sistemas). Um testador atento exploraria os efeitos de usar valores de objeto ou de cadeia de caracteres para arcHeight e skew.

Testar o parâmetro color é semelhante a testar os parâmetros start e finish. Entretanto, é recomendável testar o valor padrão omitindo o parâmetro color na chamada da função. Observe que, devido ao JavaScript passar os parâmetros por posição, se você omitir o valor do parâmetro color, deverá também omitir todos os valores de parâmetro subsequentes (width, upDown e numSegments). Dito isso, omitir color, mas depois fornecer valores para um ou mais dos parâmetros finais, surtiria o efeito de valores de parâmetro desalinhados, e isso é algo que um testador experiente examinaria.

Devido aos parâmetros width e numSegments representarem medidas físicas, bons testadores certamente tentariam valores de 0 e valores negativos para width e numSegments. Como esses valores de parâmetro são tidos como inteiros, é recomendável tentar valores numéricos não inteiros, como 3,5, também.

Se você examinar o código de AddBezierCurve, notará que, se o valor do parâmetro upDown for algo diferente de “up”, a lógica da função interpretará o valor do parâmetro como “down”. Isso levaria um testador experiente a imaginar o comportamento correto da cadeia de caracteres vazia e nula e de uma cadeia de caracteres consistindo em um único espaço. Além disso, valores numéricos e de objeto para upDown devem ser testados. Um testador experiente se perguntaria se o desenvolvedor tinha a intenção de que o parâmetro upDown diferenciasse letras maiúsculas de minúsculas; um valor "UP" para upDown poderia ser interpretado como "down".

As funções de API geralmente são adequadas para testes automatizados usando entradas aleatórias. Você pode usar uma das diversas técnicas para gerar valores aleatórios programaticamente para start, finish, arcHeight e skew, enviar esses valores à página da Web e verificar se ocorrem quaisquer exceções.

Duas finalidades

Recapitulando, esta coluna tem duas finalidades. A primeira é apresentar uma função de JavaScript prática para criar uma curva de Bezier em um controle de mapa do Bing Maps AJAX com a lógica de código subjacente. Se você já trabalhou com mapas, deve achar a função AddBezierCurve apresentada aqui um recurso muito útil. A segunda finalidade da coluna é apresentar as diretrizes de testes de uma função de JavaScript não trivial. Percebemos que há várias coisas que você deveria verificar, incluindo valores nulos ou ausentes, valores ilegais, valores-limite e valores com o tipo incorreto. Esses princípios são verdadeiros para funções de teste de API/módulo escritas na maioria das linguagens de programação.

Dr. James McCaffrey trabalha para a Volt Information Sciences Inc., onde gerencia o treinamento técnico de engenheiros de software que trabalham no campus de Washington da Microsoft Redmond. Ele trabalhou em diversos produtos da Microsoft, incluindo o Internet Explorer e o MSN Search. James McCaffrey é autor de “.NET Test Automation Recipes” (Apress, 2006) e pode ser contatado em jammc@microsoft.com.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Paul Koch, Dan Liebling, Anne Loomis Thompson e Shane Williams