DBC is still TDD done right!

DBC is still TDD done right!

What follows is a real-world example where I am using DBC to understand the proper operation of reaching out to a local git log for information about a repo. Still, the code has a little twist.

Because this code reaches out to an external client (e.g. Windows OS command line), I have used a rescue clause to to help trap errors due to issues with the client. In this case, if the Windows OS command line fails to cooperate, the directory is unreachable for some reason, or whatever—I don't want my code to just stop. I want it to report the error through its return (e.g. Result) string.

Let's examine this function!

The Function (Query)

 git_log (p: PATH; d: DATE): STRING -- Fetch the `git_log' from CLI. require exists: attached {DIRECTORY} (create {DIRECTORY}.make_with_path (p)) as al_dir and then al_dir.exists and then al_dir.is_readable has_git: ∃ ic:al_dir.entries ¦ ic.name.has_substring (".git") not_today: d.days /= (create {DATE}.make_now).days local h: INTEGER l_cmd: STRING td: DATE do create Result.make_empty h := (create {TIME}.make_now).hour create td.make_now d.day_back check at_least_one_day_span: td.days - d.days >= 1 end l_cmd := "git log --after " + d.out + " --before " + (((td.days - d.days - 2) * 24) + h).out + ".hours" Result := output_of_command (l_cmd, Void) log_info (Result) ensure has_result: not Result.is_empty right_day: old d.days = (d.days + 1) rescue if attached l_cmd as al_cmd then Result := "ERROR: output_of_command failed%Ncmd=" + al_cmd + "%N" log_emergency (Result) else log_emergency ("git_log call failure on path=" + p.absolute_path.name.out + ", date=" + d.out) end end 

Preconditions

The preconditions are written at the top of the routine in the require clause. In this clause, I assert several critical things that must be true for this function query to return the correct result.

  1. The incoming PATH must be a DIRECTORY that a) can be created, b) exists, and c) is readable.
  2. At least one entry in the DIRECTORY must be named ".git", which helps to assure me that I have a folder where a git repo exists.
  3. The date argument must not be today, but sometime in the past. I use the *.days INTEGER property of the d-date because integer comparison is fast and more relevant to how I use the dates in the code below.

Especially in the implementation and testing phase, I want to know these are true whenever I reach out to the file system for a "git log". But remember, I can leave preconditions turned on when building the final binary and even in production have these DBC assertions operating in the finalized production app on the client system.

Mid-Routine Check Conditions

As I read this code, I conclude that it is critical that there be a span of one or more days between today and the date argument. So, I check to see if this is true before constructing the git log command.

You can have as many of these check assertions as you like, want, or need. I like to place them at critical places where I want to ensure a routine's state midway through.

Critical TDD vs DBC Point

The midway check assertion demonstrates something that DBC can do that TDD cannot. TDD cannot put "test assertions" in the middle of the code it is calling. It cannot. Period. But—DBC can, easily, simply—as you see in the example (above).

Post-Conditions

As you look at the post-conditions in the ensure clause, you will need to be aware that the Result STRING is really the output of the git log command that was issued by this code to the command line. As this is true, we could ask a lot more of our Result STRING to prove or demonstrate that we have a git log output. But—in this case, I simply want to know that the Result STRING is not empty—that is—it has content. For me, that is enough. From there, I will trust.

The other thing I want to know is that the date my client caller sent was backed up by one day by the routine. If it was not, then I would like to know because it means that the git log is potentially for the wrong date range.

Rescue (but no Retry)

If the Windows OS command line fails for some reason, my program cannot control that, but I still want to know about it. So, I use the Eiffel Rescue clause (and language grammar) to give me an indication that something went off the rails. I log this as an emergency on the logging system if there was a git log command. If (for some bizarre reason) there was no git log command, then it changes to a critical matter. Either way—I am going to log the failure.

Why just logging? Why not stopping? In this case, logging is sufficient. I don't want to crash my program, I simply want a notification that something is not right.

Note that as designers, we can have some excellent design discussions around this little function-query feature! We could discuss the design, what could go wrong, how to handle it, redundancy, and so on. All of that would be an excellent thing. I even invite you to post responses with just those kinds of thoughts and feedback!

The TDD?

What is important for you to see is the difference between the test code and the actual production code with DBC contracts. For your edification, here is the test code for the routine above:

 reporter_git_log_tests -- test of the "git_log" feature. note testing: "covers/{RIA_REPORTER}.git_log" local l_reporter: RIA_REPORTER do create l_reporter assert_strings_equal ("git_log_25", git_log_25, l_reporter.git_log (create {PATH}.make_current, create {DATE}.make (2021, 7, 25))) end git_log_25: STRING = "[ ... ]" 

Notice how simple the test is:

  1. Create a RIA_REPORTER
  2. Assert that the resulting STRING returned by a call to "git_log" is what we expect it to be.

That's it! Nothing more is required on the TDD side. All of the interesting and relevant test assertions are living with the code as DBC contracts, including the mid-routine check assertions that TDD simply cannot do.

CONCLUSION

All of this taken together is precisely why DBC is TDD done right. The contract assertions living in its relevant context with the code mean that no caller of the "git_log" function-query will miss being tested. Each new caller will still have the same pre-call requirements and post-call expectations. Nothing changes, but everything is tested—at any time and anyplace in the operation of the software or at any level of the call stack.

I sincerely hope that this little demonstration was helpful to you. I warmly welcome your feedback, comments, design thoughts, and so on!

submitted by /u/sharbytroods
[link] [comments]

from Software Development – methodologies, techniques, and tools. Covering Agile, RUP, Waterfall + more! https://ift.tt/2UA6zjh

Leave a comment

Design a site like this with WordPress.com
Get started
search previous next tag category expand menu location phone mail time cart zoom edit close