Объект "Продажа" не должен ничего знать про разные валюты, курсы и правила конвертирования. Цена должна быть не числовым полем, а объектом. Такой объект должен уметь выражать себя в любой валюте. Причем таблица курсов валют может быть не связана с классом "Цена", а быть самостоятельным классом.
Тесты:
1) Positive case: price = 100, rate = 1.5
2) Граничные значения для price и rate (независимо): 0, -1, 1, Decimal.MaxValue, 0.0000000000001. Каким должен быть результат в случае неверных данных, если это не исключение? Правильный ли тип имеет исключение? Несет ли сообщение правильную информацию об ошибке? Можно ли вызвать Convert снова после исключения?
3) Переполнение: price = (большое число), rate = (большое число), так что price * rate > Decimal.MaxValue. Аналогично для потери точности, только там маленькие числа.
4) Округление: каков смысл результата "1.0000000000001 рубля", "5.4999999999999999 рублей", "10.50000000001 рублей"? Как задается количество значащих цифр результата и правила округления? В бухгалтерии правила округления могут не совпадать с математическими.
5) Симметрия: Convert(A USD to RUR) = B, Convert(B RUR to USD) = A. Можно повторить в цикле много раз и проверить на потерю точности.
6) Thread safety: вызывать конверсию из разных потоков, убедиться в том, что результат всегда один и тот же.
7) Стабильность: вызвать Convert несколько раз с одинаковыми параметрами, результаты должны совпадать.
8) Singleton: а не самописный ли синглтон реализация конверсии? Проверить типичные ошибки при реализации синглтона.
Кстати, в первой части книги Кента Бека "Экстремальное программирование: разработка через тестирование" как раз используется денежный пример и хорошо вообще описывается разработка через тестирование (это не метод тестирования, это метод разработки). Короче, советую ознакомиться хотя бы с первой частью.
Кстати, он там очень элегантно избавился от классов для различных валют!