I would probably do something like this:
d = {2, 3, 4, 5}; c = {1, 2, 3, 4, 5, 6, 7, 8};
frac := RandomChoice[d]/RandomChoice[c];
With[
{frac1 = frac, frac2 = frac},
FormFunction[{"first" -> <|"Interpreter" -> "Number",
"Label" -> Row[{frac1, " + ", frac2}]|>}, f,
AppearanceRules -> {"SubmitLabel" -> "Go!"}
]]
The SetDelayed
frac := RandomChoice[d]/RandomChoice[c];
Will cause the RandomChoices to be evaluated anew each time frac is encountered. And there was no need for the Module stuff.
By defining frac1 and frac2 as part of the With, you now have constant values that you can use more than once without re-evaluating the RandomChoices. It's not strictly necessary for the example you provided, but as things evolve it might be useful.
The actual display bit uses Row
Row[{frac1, " + ", frac2}]
but there are other options. The point here is that the "+" will invoke Plus. It will never be interpreted as a raw token to be left un-evaluated. You are wanting to see an actual "+", so that will probably need to be a string. So what you really want is to assemble three independent pieces together to look like an arithmetic expression. You could come up with your own ToString-like function for the fractions and StringJoin them together, but I just took the simple route of using Row.