In some cases we write functions that have a dual operation, for example *encode* and *decode*, *parse* and *pretty\_print*, or *convert\_to* and *convert\_from*.
Developing both functions at the same time gives an advantage when testing, since one of the properties that one is interested in is
encode(decode(X)) == X.
Surely, one could easily implement functions that guarantee this operation and are still doing the wrong thing, thus only this property is insufficient for testing. That said, it still is a good idea to also check this property and many bugs have been found by simple properties like this.
Erlang R12B has a module base64 in the standard library http://www.erlang.org/doc/man/base64.html. The module has a number of function, among which *encode* and *decode*.
We write the obvious property. Just read the manual: Encodes a plain ASCII string into base64. Use http://en.wikipedia.org/wiki/ASCII wikipedia to remind yourself that a plain ascii character is a value between 0 and 127. Extended ascii goes to 255.
prop_identity() -> ?FORALL(Data,list(plain_ascii_char()), base64:decode(base64:encode(Data)) == Data). plain_ascii_char() -> choose(0,127).
We can run QuickCheck to find out that we haven't specified clear enough.
eqc:quickcheck(base64_eqc:prop_identity()). Failed! After 1 tests.  false
The authors of the module base64 allow the input of encode to be either a binary or a string, but the result is always a binary. As a result, the encoding of the empty string returns the empty binary.
base64:decode(base64:encode("")). <<>> base64:decode(base64:encode("hello")). <<"hello">>
Seems that we should be more successful if we check that the result is the right binary and more precise is we also allow binaries as input. This makes the specification a bit more complicated and one would probably prefer two different functions if testing and specification were to go before design.
prop_identity() -> ?FORALL(Data,data(), begin Base64 = base64:decode(base64:encode(Data)), if is_list(Data) -> Base64 == list_to_binary(Data); is_binary(Data) -> Base64 == Data end end). data() -> ?LET(AsciiString, list(plain_ascii_char()), oneof([AsciiString,list_to_binary(AsciiString)])).
We can run a few hundred tests on this one and will see that they all pass. Of course, when all tests pass, one starts wondering about the quality of the input data. Inspecting a sample shows that we do test quite a variety, but the data is rather short.
67> eqc_gen:sample(base64_eqc:data()). <<"]C\t">> <<"ì">> <<"Ä ëÆ">> <<154,25,220,196,225>> [155,237] <<>> <<"kè lcJ">> "ÑV" <<97,76,162,46,216,155>> [72,127,71,113,159] <<28,129,39,246,204,83,204>> ok