Existem duas maneiras pelas quais as classes base abstratas são usadas.
Você está especializando seu objeto abstrato, mas todos os clientes usarão a classe derivada por meio de sua interface base.
Você está usando uma classe base abstrata para fatorar a duplicação nos objetos em seu design, e os clientes usam as implementações concretas por meio de suas próprias interfaces.
Solução para 1 - Padrão de Estratégia
Se você tiver a primeira situação, realmente terá uma interface definida pelos métodos virtuais na classe abstrata que suas classes derivadas estão implementando.
Você deve considerar fazer disso uma interface real, alterar sua classe abstrata para ser concreta e tomar uma instância dessa interface em seu construtor. Suas classes derivadas tornam-se implementações dessa nova interface.
Isso significa que agora você pode testar sua classe abstrata abstrata usando uma instância simulada da nova interface e cada nova implementação através da interface agora pública. Tudo é simples e testável.
Solução para 2
Se você tiver a segunda situação, sua classe abstrata estará trabalhando como uma classe auxiliar.
Dê uma olhada na funcionalidade que ela contém. Veja se alguma delas pode ser empurrada para os objetos que estão sendo manipulados para minimizar essa duplicação. Se você ainda tiver alguma coisa, tente torná-la uma classe auxiliar que sua implementação concreta leva no construtor e remova a classe base.
Isso novamente leva a classes concretas que são simples e facilmente testáveis.
Como uma regra
Favorecer redes complexas de objetos simples em detrimento de uma rede simples de objetos complexos.
A chave para código testável extensível são pequenos blocos de construção e fiação independente.
Atualizado: Como lidar com misturas de ambos?
É possível ter uma classe base executando essas duas funções ... ou seja: possui uma interface pública e métodos auxiliares protegidos. Se for esse o caso, você pode fatorar os métodos auxiliares em uma classe (cenário2) e converter a árvore de herança em um padrão de estratégia.
Se você achar que tem alguns métodos que sua classe base implementa diretamente e outros são virtuais, ainda é possível converter a árvore de herança em um padrão de estratégia, mas eu também consideraria um bom indicador de que as responsabilidades não estão alinhadas corretamente e podem precisa de refatoração.
Atualização 2: Classes abstratas como um trampolim (12/06/2014)
Eu tive uma situação outro dia em que usei abstract, então gostaria de explorar o porquê.
Temos um formato padrão para nossos arquivos de configuração. Esta ferramenta específica possui 3 arquivos de configuração, todos nesse formato. Eu queria uma classe fortemente tipada para cada arquivo de configuração, para que, através da injeção de dependência, uma classe pudesse solicitar as configurações de que se importava.
Eu implementei isso tendo uma classe base abstrata que sabe analisar os formatos dos arquivos de configurações e as classes derivadas que expunham esses mesmos métodos, mas encapsulavam o local do arquivo de configurações.
Eu poderia ter escrito um "SettingsFileParser" que as 3 classes agruparam e depois delegado à classe base para expor os métodos de acesso a dados. Eu escolhi não fazer isso ainda, pois isso levaria a 3 classes derivadas com mais delegação código de do que qualquer outra coisa.
No entanto ... conforme esse código evolui e os consumidores de cada uma dessas classes de configurações ficam mais claros. Cada usuário de configurações solicitará algumas configurações e as transformará de alguma maneira (como as configurações são texto, elas podem ser agrupadas em objetos para convertê-las em números etc.). Quando isso acontece, vou começar a extrair essa lógica para métodos de manipulação de dados e empurrá-los de volta para as classes de configurações fortemente tipadas. Isso levará a uma interface de nível superior para cada conjunto de configurações, que eventualmente não está mais ciente de que está lidando com 'configurações'.
Nesse ponto, as classes de configurações fortemente tipadas não precisarão mais dos métodos "getter" que expõem a implementação subjacente das "configurações".
Nesse ponto, eu não gostaria mais que sua interface pública incluísse os métodos do acessador de configurações; então vou mudar essa classe para encapsular uma classe de analisador de configurações em vez de derivar dela.
A classe Abstract é, portanto: uma maneira de evitar o código de delegação no momento e um marcador no código para me lembrar de mudar o design mais tarde. Talvez eu nunca chegue a isso, então pode demorar um pouco ... apenas o código pode dizer.
Acho que isso é verdade com qualquer regra ... como "sem métodos estáticos" ou "sem métodos privados". Eles indicam um cheiro no código ... e isso é bom. Mantém você procurando a abstração que você perdeu ... e permite que você continue agregando valor ao seu cliente nesse meio tempo.
Eu imagino regras como esta definindo uma paisagem, onde o código sustentável vive nos vales. À medida que você adiciona um novo comportamento, é como chover no seu código. Inicialmente, você o coloca onde quer que ele apareça. Depois, refatora para permitir que as forças do bom design promovam o comportamento até que tudo acabe nos vales.