In a React world, everything is an event that anybody can grab and process according to whatever its responsibility is. The notion of event allows for very low contract coupling; it acts as a medium between classes. A message is similar, but with more coupling as it aggregates endpoints or endpoints addresses.
This model offers flexible design where events flow and are processed through the systems. But what happens when you have too many events to process?
Based on the interesting feedback I got (can be seen on Tom’s ramblings), I realized this post probably needed some tweaking and scope precision. I did put them at the end.
What is the adequate objective for test coverage?
Like many others, I have often pondered this question, like many before me, and many after I suppose. Why aiming for 100%? The 80/20 law clearly applies to test coverage: to try to cover every corner cases that lie in the code is going to require a significant investment in time and brain cells. Plus integration point can never really be properly covered.
On the other hand, having 100% coverage provides huge benefits:
Every single line of code is constantly tested
Trust in code is high
Any uncovered line is a regression
What happens if the target is, 80%:
Significant part of the code is never tested
Trust in code is moderate and can degrade
20% uncovered line codes is significant, 2000 lines for a 10K code base. That means full namespaces can hide in there.
For me, there is no question 100% coverage is the only worthy objective, do not settle for less.
Yes there are exceptions, usually at integration points. Mocks are not a real solution either, they can help you increase your coverage but not by that much. The pragmatic solution is to wrap them into isolated modules (jars/assemblies). Think hexagonal architecture there. You will have specific coverage target for those, you also need to make sure that no other code creeps in and finally, understands those are weak points in your design.
While I am working on nFluent, I constantly make sure unit tests exert every single line of code of the library. It also means that I help contributors reach the target. It is not that difficult, especially if you do TDD!
There is one simple golden rule: to reach and maintain 100% coverage, you do not need to add tests, you have to remove not covered lines!
This is important, so let me restate that: a line of code that is not covered is not maintainable, must be seen as not working and must be removed!
Think about it:
The fact that no automated test exists means that the behavior can be silently changed, not just the implementation!
Any newcomer, including your proverbial future self, will have to guess the expected behavior !
What will happen if the code get executed some day in production?
If you are doing TDD you can safely assume the code isuseless!
So, when you discover not covered lines, refactor to remove them or to make sure they are exerted. But do not add tests for the sake of coverage.
Having 100% coverage does not mean the code is bug free
Tom’s comments implied that I was somehow trying to promote this idea. 100% coverage is no bug free proof at all, and do not imply this at all. Quality and relevance of your tests are essential attributes; that is exactly why I promote removing non tested lines. Any specially crafted test will not be driven by an actual need and would be artificial. The net result would be a less agile code base.
On the other hand, if you have 100% coverage and you discover some reproducible bug, either by manual testing or in production, you should be able to add an automated test to prevent any re occurrence.
When coverage is insufficient, there is a high probability that you will not be able to add this test, keeping the door open for future regression!
If you want to build trust based on coverage metrics, you need to look into branch coverage and property based testing at the very least. But I do not think this is a smart objective.
This post focuses on new code! For legacy code, the approach should be to add tests before anything else, and never remove working code 🙂
Full disclosure I am an active contributor to NFluent.
Imagine your first days on a new project that has a decent unit tests base (if not, change this or walk away).
You start coding away a fix or new feature, you extend the test base, you happily commit! Then the factory signals a failed build due to failing tests.
Well, that’s expected actually, unit tests are here as a safety net.
Bu then you realize that the test error message does not help. Neither taking a look at the test code, as the assertion’s syntax does not properly reflect the test intents.
Or maybe the previous developer failed to realize that the expected value comes first in Assert.AreEqual, or the comment has not been updated.
In the end, you have to debug the test to understand what is going wrong.
This hassle fuels the naysayers that claim that unit tests are a cute idea but:
do not provided added value to the product
are expensive to write
are a burden in maintenance
They are basically right. Of course this is a short-sighted vision, but those are actual issues with many code base as of now, with the notable exception of OSS.
This is a serious issue that needs to be addressed.
Part of the problem comes from the fact that unit test tools have not significantly changed in the past decade, a time when the main challenges were being able to implement test runners and build testing infrastructures. Then there was interest in building more efficient test runners with the integration of multiple scenario for a single test, or the generation of variants. But no significant effort regarding the API.
Coming back to my earlier example, we, TDD craftsmen, need an API that allows us to be expressive on the assertion/checks we make. We want the IDE to help us, typically through Intellisense, we want to be able to add our own tests, we want to express conjoined test criteria as a single check and we want to ensure that newcomers (including our proverbial ‘future self’) to understand clearly what the test is about.
nFluent has been designed to answer those requirements as well as some others. It is on OSS assertion library that works on top of all unit test frameworks (nUnit, mbUnit, xUnit, even MSTest). You can start using now on any existing code base or new project. It is available through nFluent and it is guided by the brilliant T.Pierrain (@Tpierrain).
I just finished adding a feature to a small tool that permits to define trigger time in any time zone the user wishes. It gave me the opportunity to assess the situation regarding date and time classes. The status is appalling : both C# and Java standard classes simply sucks at the exercise, for different reasons, but they lead to the ‘flatland of desolation’ instead the ‘pit of success’. And they will leave you stranded if you need more than ‘Local to UTC’ and ‘UTC to local’ conversions. This gives us an opportunity to make a tour d’horizon on the topics of Date, Time, DST and time zones. Lets start by some useful definitions…
Calendar: allows to give name to periods of time. The smallest period represented in a calendar is a day. Multiple calendars exist. Most of you are probably aware of the existence of one of those: jewish , muslim , Chinese… Keep in mind: just a representation, many calendars exist.
GMT: Greenwich Mean Time. Current time of day as viewed from the Greenwich’s meridian. Is used as the standard global time.
UTC: Universal Time Coordinated, has replaced TAI. The difference with TAI is that UTC accounts explicitly for leap seconds, whereas GMT does not. As of today, I am not aware of any library that does manage leap seconds (see below).
TAI: International Atomic Time, reference time established by averaging atomic clock outputs (>200) based on the current second definition. The acronym comes from the french: Temps Atomique International. This standard is an absolute that disregards the Earth rotation. It is roughly synchronized with GMT; there is currently a 35 seconds gap.
Leap Seconds: due to the slowdown of the earth rotation, extra seconds have to be inserted so that the actual time of day still matches its definition. There are added at the end of the day (GMT Time) as an extra second: after 23:59:59, the clock marks 23:59:60 and then 00:00:00. They are added either on June 30th or December 31st; as of today, 25 leap seconds have been added since 1972, roughly one per year up to 1998 and around 1 every 4 years since.
Time zone: geographical zone where the current time is the same. Originally, they were defined by an offset against the GMT, but since the introduction of Daylight saving time, they have got more complex.
Local time: time as seen within a given time zone.
Now it is time to discuss the pitfalls…
#1 Time vs time : same difference?
This is a big one: when the user/requirement speaks about time, what does he/it actually mean? local time or universal time? When dealing with humans, you can assume local time, but turn the implicit into explicit and raise the question. When dealing with MtoM, universal time is your best bet, unless humans are somehow involved. Next questions is: are multiple time zones involved? Probably, most systems are global in some way. The use case probably needs refining, such as: As a user I want this process to happen at 10 AM Paris Time and 4 PM Hong Kong. Pretty straightforward, don’t you think? Nope! Make the implicit explicit: Does it mean simultaneously? Yes, of course, as it happens actually once at 10 AM Paris, so 4PM Hong Kong. Are we there yet? Nope!!! It turns out that 10 AM Paris may not be equivalent to 4PM Hong Kong, because Paris has Daylight Saving Time and Hong Kong does not: half of the year the gap is 6 hours, and it is 7 hours for the other half. So, now the question is: is the reference Paris time (10 AM) or Hong Kong time (4 PM)? Or it may also be 8 AM UTC? Conclusion: When dealing with time, make sure to properly identify the time zone.
#2 A date is not defined at midnight
In C# there is no Date only type, therefore the (enforced, see DateTime.Date) usage is to set the time to 00:00, as in March 23rd is 2015/03/23 00:00:00 when you have no concern for time.
Bad! As soon as you will do any kind of transformation: arithmetic, TimeZone conversions etc.. you have a significant risk to loose a day in the process . For example, if the initial date is the end of DST, moving forward by 1 day (i.e. 24 hours) will stay on the same date, at 11 PM. Search for a Date only class.
#3 Full UTC may not save you
Once you have been bitten by some TimeZone issues, you may be tempted to go full UTC, i.e. having all internal dates expressed as UTC datetimes. Alas! As soon as you need to relate those to the user you can either show those as UTC or his/her current local time. But this may lead to some unexpected results if the user has changed timezone for some reason, or entered/exited DST. Conclusion: store the reference timezone whenever you need to store a DateTime or a time (without the date).
#4 Scheduling is a nightmare
Well, any type of scheduling is more complex than one may think: 99% of the time, this will be implemented by computing the delay before the event occur and then awaiting that delay to elapse. Maybe someday OSes will natively provide date&time based scheduling, but for now, you need to deal with self computed wait time and delays. To compute a delay, you need to use the same base, i.e. same timezone, for both the start time and the scheduled event, then the delay can be used as is for any timer primitive that suits your need.
Remember that a user’s timezone is not a constant attribute: when I travel, I no longer expect my phone to wake me up at 6AM Paris time, it would make me very angry when I am in New York!
#5 There is no shared standard for timezone identification
Microsoft OSes were among the first to fully support timezones, i.e. not only supporting the standard time offset but also daylight saving time period. And they have established their own timezone referential. But due to its proprietary dimension, it has never got any traction outside Windows. To be fair, it exhibits shortcomings, the biggest one being it they has poor ids for the timezones.
But there is a good alternative: the IANA time zone database. It does offer nice ids, based on regions and cities and it is used on Java, iOS, Linux… basically everywhere but Windows. But the naming convention is not practical for storage communication: it is Area/Location, such as Europe/Amsterdam which is a bit long to be included with each exchanged date and impact storage/bandwidth requirements.
#6 There is no adequate framework for unit testing
Not sure what the situation is for Java, but trying to simulate/set a timezone in unit testing is hard, to say the least. And it gets even harder if you need two: one for the server and one for the (mock) user!
Rules of thumb
Store application events in UTC
Properly identify users timezone
Use a date only class if you only need dates
Schedule event in the user timezone
Noda Time: .Net version Joda time, excellent site with clear explanations
Joda Time: Java Date/Time library with proper support for time zones (among many other features)