The 0.272 is the expected value in the unit test, so that’s okay. The probability you are calculating in the experiment is usually around 0.006, which is the problem. Your experiment code:

def experiment(hat, expected_balls, num_balls_drawn, num_experiments):
count = 0
exp = copy.deepcopy(hat.contents)
len_exp = len(exp)
for n in range(num_experiments):
prova = hat.draw(num_balls_drawn)
for v in expected_balls.keys():
count = 0
for x in range(len_exp):
if exp[x] == v:
count += 1
if count < expected_balls[v]:
#count -= 1
break
return count/num_experiments

needs to make a copy of the provided hat for each experiment. Also, you are resetting count each iteration of the interior for loop, which I don’t think is what you intend. Also, you are comparing expected_balls to the contents of the hat since you have defined exp = copy.deepcopy(hat.contents). If you add some print statements and look at the output

def experiment(hat, expected_balls, num_balls_drawn, num_experiments):
count = 0
exp = copy.deepcopy(hat.contents)
len_exp = len(exp)
for n in range(num_experiments):
prova = hat.draw(num_balls_drawn)
print(f'drawn: {prova} expected: {expected_balls}')
for v in expected_balls.keys():
count = 0
# Comparing to hat.contents, not prova!
for x in range(len_exp):
if exp[x] == v:
count += 1
if count < expected_balls[v]:
print(f'bad experiment, prob: {count}/{num_experiments}')
#count -= 1
break
else:
print(f'good experiment, prob: {count}/{num_experiments}')
return count/num_experiments