Your goal is to test shuffle(). Since you know how you constructed shuffle(), it would be a deterministic unit test of your initial deck vs. the shuffled deck if you were able to know the series of numbers generated.
This is a case where injecting a method into your Deck() class during testing can make your shuffle function deterministic.
Build your class to use the random() function by default but to use a predetermined, number generating function when injected. For example, in Python you can do:
class Deck():
def __init__(self, rand_func = random.random):
self._rand = rand_func
def rand(self):
return self._rand()
When simply using Deck with no arguments, you get the random number expected. But if you craft your own random number function, you can generate your predetermined sequence of numbers.
With this construction you can now build an initial deck (whatever size you want) and a list of random numbers (again, whatever size you need) and you will know what to expect as output. Because shuffle() doesn't change between the injected version and the truly random version you can unit test shuffle() deterministically and yet have random behavior at runtime. You can even generate multiple different number sequences if there are corner cases you want to test.
With respect to the other's answers involving statistical modeling: I think those are acceptance level tests to prove the correctness of the "shuffle" algorithm, but it doesn't deterministically unit test the function shuffle()'s implementation.