Criar um novo software é sempre uma oportunidade para utilizar as mais novas técnicas e tecnologias. Para acelerar o processo normalmente recorremos a bibliotecas que nos possam ajudar. Ironicamente estas são as mesmas bibliotecas que serão responsáveis pela obsolescência do software e eventualmente seu abandono.
Já falei neste blog sobre as diretivas de arquitetura que precisam ser seguidas em softwares, especialmente nos novos. Bibliotecas de terceiros devem ser isoladas de forma a permitir lidar com bugs nas bibliotecas e a sua substituição por outras, especialmente quando os bugs levam a vulnerabilidades de segurança. Então, tenha em mente que para cada item da lista a seguir, seria uma boa ideia isolar a biblioteca escolhida.
A seguir, uma lista das áreas que precisam ter um bom suporte de bibliotecas sejam elas criadas por si, ou externas. Separei entre áreas essenciais que todos os sistemas têm, algumas área frequentes e algumas opcionais apenas para certos tipos de software.
Deve ter reparado na expressão “boa biblioteca”. O conceito do que é uma boa biblioteca tem evoluído ao longo do tempo e continuará evoluindo. Algumas propriedades como expressividade – permite expressar a intenção do programador facilmente -, performance – é rápido e/ou ocupa pouca memória – e suporte – bem documentado e/ou com possibilidade de fazer perguntas – são necessárias em mais ou menos todos os tipos de bibliotecas. Simplicidade de uso também é um fator, mas podemos usar bibliotecas um pouco mais complexas se isso ajudar os outros fatores.
Nem sempre as bibliotecas disponíveis, as mais usadas, as que têm melhor suporte são baseadas em princípios corretos de design. Estes princípios, embora sempre importantes, se tornam mais, ou menos, importantes com a moda dos tempos. Por exemplo, a biblioteca moment.js é muito conhecida e utilizada no mundo da programação em JavaScript/TypeScript. É e fato muito útil para cálculos com horas e datas. Contudo o objeto moment não é imutável o que causa vários bugs e exige uma atenção acima da média. Alternativas como o Joda Time JS não têm este problema.
O ponto é que uma biblioteca que parece ótima em um momento do tempo, pode não ser tão ótima depois. Algumas resistem ao passar do tempo como o Spring ou a Apache POI, entre outros e se procuramos estabilidade acabamos sempre escolhendo essas bibliotecas que já temos confiança.
Contudo, como já falei em outros artigos, sempre precisamos de código para fazer a cola entre essas bibliotecas e o nosso software. É mais fácil com algumas e mais difícil com outras. Algumas demandam mais encapsulamento e algumas menos. A última milha é sempre o ponto focal mais importante porque pode fazer ou destruir o software, não no presente, mas no futuro.
Então, uma “boa biblioteca” não é apenas aquela que é bem documentada, rápida e expressiva, mas também que segue bons princípios de design – menos provável que quebre no futuro – e se presta a encapsulamento pelo nosso código.
Alguns comentários e exemplos de cada categoria
Aqui estamos falando de coisas básicas como cálculo de hash composto, operações sobre strings, operações sobre coleções e qualquer tipo de operação que estenda as capacidades do SDK da plataforma/linguagem.
Muitas bibliotecas existem, especialmente no mundo Java. Apache Commons Lang e Guava são alguns exemplos. Em linguagens com extensions methods como C#, Dart ou Kotlin este problema é menor e acaba sendo resolvido pela própria linguagem ou por uma pequena biblioteca que você mesmo constrói.
Poderia parecer que um problema tão fundamental já teria sido resolvido pelos provedores das plataformas em seus SDK. Infelizmente o suporte à representação e operação com datas e horas é mínimo na maioria das plataformas, sendo necessário utilizar bibliotecas de terceiros.
A exceção a esta regra é a plataforma Java onde este assunto recebeu bastante atenção, mas só à terceira tentativa – com a criação do java.time
, baseado no Joda Time – é que realmente o problema foi resolvido a contento.
Este assunto é especialmente complexo. Muitas regras e regrinhas são necessárias na correta contagem e cálculo de tempos, portanto não é uma boa ideia criar a sua própria API de Datas e Horas para fazer cálculos. Se está em Java, use apenas a API java.time
e pronto. Se está em outra plataforma, ou numa versão do Java sem essa API, crie uma API que simule os mesmos conceitos, mas que seja apenas uma casca para uma API de terceiros que realmente faça os cálculos. Quando essa API for substituída por outra no mercado, você pode substituir na sua implementação. Aviso amigo, é necessário um bom design da sua API para a manter isolada do motor que utilizar por baixo dos panos, mas vale a pena para manter seu código longe de bugs e obsolescência.
Existem em outra plataformas portes do Joda Time ou equivalente. Por exemplo, Noda Time para .NET, Time Machine para Dart e JS Joda para Javascript/TypeScript. Prefira bibliotecas que tenham, pelo menos, os conceitos de : Clock
, Instant
, LocalDate
, LocalDateTime
e ZonedDateTime
.
Sabemos que o uso de double
e float
não é bom para certas operações. Mesmo BigDecimal
pode não ser útil. Ou pode ser que precise de objetos matemáticos como números complexos, matrizes ou vetores, ou operações menos frequentes como Transformada de Fourier. Enfim, é muita vezes necessária uma biblioteca adequada às necessidades matemáticas do seu software que o SDK não provê.
Para operações de randomização tenha em atenção se está realizando uma mera operação matemática – como um teste de Monte Carlo – ou uma operação de segurança – como criptografia. Não use as bibliotecas matemáticas para segurança. Utilize API especificas para criptografia.
Java não tem muitas opções avançadas em matemática como Scala ou Python têm, então considere interagir com essas plataformas se necessário. Para coisas mais simples, mas ainda poderosas considere a Apache Commons Math.
Uma necessidade que se prende com a matemática, é a de representar e operar com dinheiro. Assim como datas e horas este é um assunto abandonado pelos SDK ou com suporte pífio. Existem várias bibliotecas neste tema. A Joda Money é um exemplo. Existe também uma JSR (JSR 354), com algumas implementações. Contudo, a JSR nunca realmente foi usada em produção.
Trabalhar com dinheiro é um pouco mais complexo do que parece, menos que trabalhar com datas e horas, mas mesmo assim tem seus truques. Tente criar uma API casca para uma API de terceiros que seja sabida correta.
Aqui há margem pra criar a sua própria API, especialmente se os cálculos forem simples e em uma só moeda, mas lembre-se de nunca – sob nenhuma circunstância – usar double
ou float
.
Certa vez participei de um projeto de um banco que tinha a sua própria API de dinheiro, criada pelo departamento de Arquitetura do banco. Essa API era usada em todas as aplicações construidas internamente pelo banco.. A nossa empresa tinha a nossa implementação, já que não éramos obrigados a usar a deles. Depois que o software entrou em testes, o pessoal de QA do banco detectou uma descredencia em valores monetários. Claro que nos culparam, mas claramente o problema era na API do banco que – pasme – usava float
para guardar o valor. Sim, um objeto Money que usava float
internamente. Tudo ao contrário do que você pode ler sobre este padrão. Conclusão, o banco mudou a sua API, e nós fomos ilibados de culpa- não sem muitas dores de cabeça, porque não acreditavam que o banco tivesse uma API tão fajuta como tinha. Pense em quantos erros não tinham sido cometidos por todas as aplicações do banco que usavam essa API. Até onde sei, ninguém foi despedido por essa.
É relativamente simples criar uma API de dinheiro, mas é preciso saber o que se está fazendo.
Uma boa biblioteca de dinheiro tem o conceito de representação – normalmente uma classe Money
– operações aritméticas corretas- sem perda de centavos- e, talvez uma API de conversões entre moedas.
Uma necessidade que se prende com a matemática, é a de representar e operar com quantidades e unidades como 29 kg
e 28 m
. De novo os SDK deixam a desejar neste requisito. Existem bibliotecas que cobrem este espaço, mas usam double
. Se isso não é um problema para o seu caso, considere usar uma delas. Mas, sendo que não há uma padrão de fato, talvez queria criar uma API casca em torno do conceito.
Assim como para dinheiro, existem JSR (JSR 363) para endereçar essa questão, mas assim como as de dinheiro, não são muito usadas em produção. Especialmente porque usam double
e são orientadas à especificação Java para operações em tempo real (Real-Time Java)
Nem todas as aplicações precisam desta API com o mesmo nível de implementação. Algumas só precisam representar uma quantidade – sem cálculos. Algumas os cálculos são simples, sem conversões. E claro, alguma precisam se todas as funcionalidades. A sua API casca pode evoluir conforme suas necessidades evoluírem.
Uma boa biblioteca de quantidades tem o conceito de representação – normalmente uma classe Quantity
que usa uma classe Unit
– operações aritméticas corretas – inclusive entre unidades – e, talvez uma API de conversões entre quantidades.
Esta API visa representar a cultura, normalmente do usuário. A cultura irá nos ser útil para saber que língua ele fala, escreve e lê , mas também como formata quantidades e dinheiro. A cultura é normalmente composta por um código de linguagem e um código de país. Por exemplo, pt_PT é o português de Portugal enquanto que pt_BR é o português do Brasil.
O formado do numero 2345 em pt_PT é 2345 e em pt_BR é 2.345 – pois em Portugal se o numero do segundo grupo é menor que 10, não se coloca separador. Mas 23456 seria 23 456 em pt-PT e 23.456 em pt_BR. Se existissem decimais seria 23 456,78 em pt_PT e 23.456,78 em pt_BR. Em en_US seria 23,456.78 já que os pontos são trocados pelas vírgulas.
Enfim, não se trata apenas de traduções, mas também de formatações. A Cultura pode ser ainda mais especifica a um estado, região, empresa ou até usuário. Contudo, não é comum trabalharmos com essas variantes.
Java contém a classe Locale
para este conceito, C# contém a classe Culture
. Algumas plataformas nem sequer têm esse conceito e você precisará criar uma classe para ele, ou usar de terceiros.
As aplicações modernas devem contar com um mecanismo de globalização de textos. Ou seja, os textos mostrados ao usuários não podem ser fixos no código e devem ser obtidos conforme a cultura do usuário tentando usar a cultura correta ou a mais aproximada possível.
Este tipo de mecanismo não é encontrado em SDK, ou quando existe é bem limitado. Java faz um bom trabalho neste campo e foi umas das primeiras linguagens/plataformas a aplicar o conceito.
Uma boa biblioteca de tradução permite obter o texto final através da cultura do usuário, de forma que dados possam ser injetados nesse texto através de variáveis. Tanto melhor se essas injeções, elas mesmas, forem formatadas conforme a cultura utilizada. Além disso, uma boa biblioteca de traduções permite variantes de plural e gênero das palavras.
As aplicações modernas devem contar com um mecanismo de globalização de de formatos. Ou seja, os valores de quantidades, dinheiro e momentos do tempo nunca são transformados para textos de forma fixa no código. Os formatos são pesquisados pela cultura do usuário para aquele tipo de quantidade.
Este tipo de mecanismo não é encontrado em SDK. Java faz um bom trabalho neste campo e foi umas das primeiras linguagens/plataformas a aplicar o conceito de Formatador. Um objeto que passar – conforme a cultura indicada – de um objeto para a sua representação textual e de volta.
Uma boa biblioteca de formatação permite obter o formato final através da cultura do usuário. Algumas bibliotecas apenas permitem que seja informado o padrão de formatação, mas não têm conhecimento de qual padrão vai com qual cultura. Isto é importante e muito útil.
Uma biblioteca de validação tem por objetivo , dado um objeto de uma classe qualquer, determinar se esse objeto é válido, ou não, em um determinado contexto. O mesmo objeto, com o mesmo estado, pode ser válido em um contexto e inválido em outro.
Uma biblioteca de validação pode ser mais simples – apenas informando se um objeto é válido no contexto – ou mais sofisticada – informando por quê o objeto não é válido naquele contexto. Quando a biblioteca informa a razão da invalidação, é bom que o faça de uma forma globalizada para que uma tradução da razão seja apresentada ao usuário na cultura que ele entende. Algumas bibliotecas existem no mercado mas que não cumprem este requisito de serem globalizadas e por isso seu uso é bem limitado a aplicações que não precisem de globalização.
Outro ponto importante é que a biblioteca assente fortemente em composição de forma que validadores mais complexos possam ser construídos compondo validadores mais simples.
Pode ser interessante também que a biblioteca distinga a severidade dos problemas encontrados e/ou indique se a validações não foi possível. Isto é especialmente útil se a validação é feita por sistemas externos.
Nem sempre as bibliotecas de mercado para esta funcionalidade atendem a todos os requisitos, mas é relativamente simples criar a sua própria biblioteca de validação.
Aqui, conversão se refere à transformação entre tipos de classes mantendo a mesma informação. Por exemplo, converter de string
para int
. Uma biblioteca de conversão entre tipos é importante em um software construído em qualquer arquitetura plana, em que é muito comum ter que converter a informação de um tipo X de uma camada para um tipo Y de outra camada.
Uma boa biblioteca de conversão deve converter os tipos básicos, e permitir-nos escrever nossos próprios conversores para os tipos mais avançados, especialmente beans. Seria interessante se permitir conversões mais avançadas como por exemplo entre LocalDateTime
e ZonedDateTime
em que o Clock do sistema é relevante.
O registro de ações do programa – logging – é essencial para sabermos o que o software está fazendo e especialmente quando algum problema é encontrado.
Existem muitas bibliotecas de logging para cada SDK e é sempre bom escolher a melhor. A SLF4J é bem famosa em Java – embora a sua expressividade deixe muito a desejar – é rápida e já por si mesma uma casca para outras API reais como Lombok ou Log4J ( muito usado outrora, mas hoje obsoleto)
Mais uma vez o SDK java oferece uma API de logging, mas o uso de API de terceiros ainda é o mais frequente. Em algumas plataformas, como JavaScript, não têm uma boa fundação para logging já que o código roda no navegador, na máquina, do usuário final. Plataformas como Android e Flutter têm um problema semelhantes. Nestes casos talvez você queira ter uma API de logging que possa usar dentro do código, mas que no fim enviar os dados para algum tipo de servidor central onde você possa consultar o que está acontecendo.
Uma boa biblioteca de registro permite vários níveis de registro e permite destingir quando o stack-trace de uma exceção será logada junto, ou não. A maior parte também permite injetar informações nas mensagens de forma simples e diferida (ou seja, a injeção só é realizada se realmente a mensagem for,de fato, ser registrada)
Em aplicações modernas, é necessário que o software se ajuste dinamicamente ao ambiente em que está sendo executado. Isto é parcialmente resolvido usando linguagens baseadas em Máquinas Virtuais (VM), mas mesmo assim, por vezes ainda é necessários distinguir se estamos em Linux, ou windows, ou dentro do navegador.
Por outro lado, o código é executado em diferentes configurações. Frequentemente ele se comporta diferente em ambiente de produção do que em ambiente de testes ou de desenvolvimento. Mesmo que o comportamento seja igual é possível que URL e parâmetros de serviços externos sejam diferentes de ambiente para ambiente. Por exemplo, o url do banco em produção não é o mesmo que em staging.
Uma boa biblioteca de ambiente permite reconhecer todos estas diferenças de forma simples, ao mesmo tempo que permite uma governança simples para entender quais são os parâmetros lidos do ambiente.
Não apenas a execução do seu código dependerá do ambiente onde é executado, mas também do ciclo de vida das funcionalidades; se já foram liberadas, se estão em testes, etc. Por vezes, você está trabalhando em uma funcionalidades que deve ser visível, por exemplo, em UAT, mas não em produção. Ou, por exemplo, uma funcionalidade que só deve estar disponível em certas épocas do ano, ou para certo grupo de usuários, ou se for acessada em certas aplicações cliente.
A capacidade de controlar dinamicamente quais partes do software estão habilitadas e quais não, é muito útil, especialmente se você é adepto de CI/CD
Em algum momento seu software irá precisar de um banco de dados. Hoje em dia, existem muitos provedores e estilos de onde escolher: SQL, noSQL, newSQL.
Seja qual for o banco, ou bancos, que escolher, vai precisar de uma biblioteca para interagir com eles. O Spring Data é uma opção frequente em Java, assim como o uso de Hibernate ou JPA. O mais relevante aqui é a API de Queries. É necessário ter acesso a uma boa API de queries de forma a isolar a intenção do programador, da real execução e otimização da pesquisa dos dados.
Lembre-se que noSQL tem tendência a não ser relacional, ou permitir relacionamentos ad hoc que não são tão otimizados como num banco relacional. Se pretende gerar relatórios ou pesquisas que, de alguma forma, cruzam os dados, noSQL não é bom para isso, o que vai se refletir numa pior API para escrever suas queries.
É importante que sempre destingia as classes usadas para persistência das outras classes do sistema. Não utilize as classes de persistência em todas as camadas. Uma boa biblioteca de conversão é importante, mas é melhor deixar a conversão de dados persistidos para modelo à parte da conversão entre tipos. Aqui é melhor usar mapeadores de dados.
Nem sempre é possível ler todos os dados que existem no banco de uma só vez e colocar tudo num objeto List. Uma mecanismo que permite obter subconjuntos – páginas – dos dados é frequentemente necessário. Ele é mais restrito pois exige que os dados possam ser ordenados de alguma forma, mas permite criar UI mais responsivas pois não é necessário ler e manter todos os dados existentes no banco. O conceito de paginação também pode ser usado para criar efeitos de infinit-scroll onde , para o usuário, parece não haver páginas, mas elas são usadas para otimizar a interação e poupar recursos.
O Spring, por exemplo, tem a classe Page para isto que cumpre alguns dos requisitos. É uma opção, mas não é a melhor, pois não tem mecanismos de transformação de itens.
Uma boa biblioteca de paginação permite que páginas sejam criadas de diferentes fontes e os itens possam ser transformados como métodos que atuam como Stream.map
em cada elemento da página. Isto é de especial interesse se o objeto Page vai atravessar diferentes camadas, cada uma com um tipo diferente de dado, o que é muito comum.
A paginação é flexível. Usar List
é isomórfico a usar Page
contendo todos os dados, então é possível simular o uso de List
com uma biblioteca de paginação.
Muitas vezes queremos proteger o sistema preservando as alterações feitas, ou pelo menos registrando o histórico de alterações. Às vezes essa informação é mostrada ao usuário, mas também pode ser usada apenas internamente. É especialmente quando algum stakeholder acusa o software de algum erro. O registro permite mostrar que foi um humano que modificou os dados erroneamente.
Este mecanismo de registro está normalmente muito vinculado com a biblioteca de acesso a dados que escolher no ponto anterior. É essencial que este processo de registro seja o mais automático possível não apenas para garantir a simplicidade para o programador, mas especialmente para garantir que sempre será executado e que os dados registrados realmente representam a realidade e nada se perdeu.
Este mecanismo, normalmente precisa do conceito de Transação, então ao escolher o seu banco de dados, tenham a atenção se ele suporta este conceito.
Esta é uma funcionalidade muito requisitada pelos usuários em sistema corporativos de registro. Os usuários querem que os dados persistidos sejam cruzados e informação seja obtida e disponibilizada na forma de relatórios impressos – hoje em dia, em PDF e não mais em papel.
Criar documentos com layout é complexo e precisamos usar bibliotecas especiais. Em Java temos o JasperReports, em outras plataformas nem sempre existirá uma biblioteca de mercado. Então escolha a sua plataforma tendo isso em consideração.
Relatórios normalmente existem agrupamento de dados e cálculos. A biblioteca de acesso a banco de dados e querie, precisa não apenas permitir estas operações – order by
ou group by
, mas também a sua execução rápida. Se tiver que ler e processar milhares de objetos na memória para produzir um relatório essa não será a melhor escolha. Mesmo quando usando noSQL é comum copiar os dados para um banco relacional para aumentar a performance de pesquisa de dados cruzados e relatórios.
Exportação é diferente de gerar relatórios. Aqui, o objetivo é permitir acesso aos dados em si, de uma forma que o usuário final os possa manipular e utilizar manualmente ou em outros softwares. Exportações de dados para Excel são muito comuns, assim como no formato CSV. O formato XML é por vezes usados para domínios específicos.
Bibliotecas especiais para exportação são muito úteis neste cenário. Para Java e C# temos a Apache POI para lidar com documentos em vários formatos.
A importação é o outro lado da moeda da exportação. Embora menos comum, é possível usar as mesmas ferramentas usadas para exportar, para importar os dados, fazendo o caminho contrário.
Contudo, ao importar é muito frequente que o usuário tenha usado o formato erroneamente e precisamos validar os dados e o formato antes de realmente aceitarmos os dados.
As bibliotecas aqui, são normalmente as mesmas usadas para exportação.
É muito comum que o software tenha que realizar tarefas agendadas do tipo “todos os dias às x horas, faça…”. Normalmente os containers proveem algum tipo de tecnologia para realizar estes agendamentos e controlar o fluxo do agendamento – por exemplo, cancelar. Em Java esta é uma funcionalidades mais ou menos comum provida, por exemplo, pelo Spring, ou qualquer container EJB. Em outras plataformas pode ser um pouco mais complexo. Por exemplo, no C# e em mobile é preciso contar com o apoio do OS sendo que cada plataforma tem especificidades.
Independentemente da plataforma estabeleça uma API que isole a tarefa que você quer executar, do processo que disparou essa execução. Isto por que há coisas a se preocupar no meio, por exemplo, logging, feature flags, controle de exceções e autorizações.
Não há , até onde sei, API especializadas apenas no isolamento, normalmente elas controlam as duas partes – o disparo e a execução da tarefa. Conforme a sua plataforma e container, escolha a que mais facilmente permitir criar o seu isolamento, e adicionar as necessidades ortogonais referidas.
Sempre uma necessidade nos dias que correm, o software tem que poder comunicar utilizando envio de e-mail. Recebimento de e-mail é mais incomum, mas possível. O envio de e-mail depende, uma vez mais da plataforma e pode ser bem complexo, então é bom ter uma API de terceiros para realizar esse envio.
Contudo, a geração do texto do e-mail pode ser também um desafio. Talvez o mecanismo de tradução não funciona tão bem aqui já que é mais voltado a traduzir frases curtas e não textos longos. Outras bibliotecas adicionais podem ser necessários para a geração dos textos de uma forma globalizada.
Há semelhança do e-mail, a notificação é um recurso muito utilizado hoje, seja por SMS ou internamente no sistema. É um mecanismo também muito utilizado do lado da aplicação cliente para chamar a atenção do usuário, especialmente no ambiente móvel.
Se o seu sistema vai utilizar estes mecanismo tenha em atenção que a biblioteca seja agnóstica à tecnologia utilizada de forma que possa usar mais que uma, simultaneamente. Tenha também atenção ao suporte em relação às suas tecnologias pois a mesma tecnologia tem que ter suporte pela plataforma da aplicação servidora e da aplicação cliente. Se você tem mais de uma tecnologia cliente, todas devem suportar a tecnologia escolhida.
Este é um tipo especial de notificação que o cliente envia para o servidor sempre que o usuário realiza alguma ação como apertar um botão ou abrir uma tela. A ideia é que rastreando e analisando os dados, podemos obter informação sobre como os usuários utilizam o sistema.
Para web e outros clientes online é possível usar ferramentas como o Google Analytics que rastreia a maior parte das operações, contudo sempre do ponto de vista da UI e não do negócio. Uma biblioteca de rastreamento dedicada ao necessário pode ser mais especifica, e portanto, oferecer mais insights.
Para ter um software de qualidade são necessários testes. Existem vários tipos de testes e para cada um tipo de ferramenta diferente.
A família xUnit de frameworks de testes é bem conhecida e existe em praticamente todas as plataformas. Estes são testes – unitários – em que esperamos um resultado após instruir alguma unidade – objeto, camada, sistema – a realizar alguma tarefa.
Temos depois os testes de velocidade – microbenchmarking – em que queremos comparar uma implementação em relação a outra com base no tempo que demora a execução. Este é um tipo de testes muito complexo de realizar em linguagens/plataformas baseadas em Maquinas Virtuais (VM), porque o código não será logo otimizado pela VM, especialmente se a execução for curta. É necessário um framework que tenha em atenção que a VM precisa “aquecer”, ou seja, realmente compilar o código e não apenas interpretá-lo.
Em Java temos a Java Microbenchmarking Harness que garante que realmente os resultados são comparáveis e executados em condições ótimas. Em C# temos a Lambda Microbenchmarking. Enfim, para a plataforma que está utilizando procure a biblioteca certa para esta finalidade. Medir o tempo de relógio que o método demorou a executar pode não ser uma medida justa se compararmos um método compilado pelo JIT com um apenas interpretado.
Além destes temos testes de carga onde um sistema externo executa e controla chamadas ao sistema alvo dos testes (SUT). Aqui o objetivo é entender quantas chamadas e respostas podem ser executadas dentro de certos cenários de recursos disponíveis como seja, número de processadores, memória e disco.
Para os testes de carga a tecnologia do sistema de teste não precisa ser a mesma que a do sistema alvo dos testes (SUT) o que nos permite uma variedade maior de ferramentas. O JMeter é uma ferramenta frequente neste uso.
Finalmente temos testes de UI onde um sistema externo exercita a UI do sistema como se fosse um usuário, tentando navegar pelo sistema e realizar tarefas como cadastros e emissão de relatórios. Este teste visa verificar que a usabilidade do software não foi quebrar por algum bug. Pode também auferir algumas a aderência a algumas métricas como seja, por exemplo, a velocidade de resposta.
Para testes de UI é normalmente usada uma conjunção de algum framework de testes unitários com uma ferramenta de guia de interação (driver); o Selenium é muito usado para esta funcionalidade. Para o caso de páginas web ou telas mobile o uso do padrão Page Model também é uma boa ideia.
Espero que seja uma lista útil se está construindo um software novo, ou até mesmo se precisa fazer alguma manutenção no que já existe. Ter boas bibliotecas de base e um bom isolamento delas, permite andar mais depressa tanto no desenvolvimento do software, tanto quanto na correção de problemas causados por essas bibliotecas, seja por bugs, por problemas de segurança, impedância ou performance.