Java, TDD True 不等于?
Java, TDD True when not equals?
出于某种原因,在 class 的一节课中测试此方法时,我们发现了一个我们无法理解的问题。出于某种原因写 System.out.println();
时它通过了吗?。有人可以解释为什么会这样吗?
public class Zones {
public ZoneId getZoneId(String input) {
if (input.equalsIgnoreCase("Stockholm")) {
return ZoneId.of("Europe/Stockholm");
}
else if (input.equalsIgnoreCase("Shanghai")) {
return ZoneId.of("Asia/Shanghai");
} else if (input.equalsIgnoreCase("Toronto")) {
return ZoneId.of("America/Toronto");
}
else if (input.equalsIgnoreCase("Hamburg")) {
return ZoneId.of("Europe/Berlin");
}
else return null;
}
public LocalDateTime getZoneTime(ZoneId zoneId) {
LocalDateTime lt = LocalDateTime.now(zoneId);
return lt;
}
}
private Zones z = new Zones();
@Test
public void getZoneTimeTest () {
System.out.println(z.getZoneTime(zIDToronto).getNano() );
System.out.println(LocalDateTime.now(zIDToronto).getNano() );
assertTrue(z.getZoneTime(zIDToronto).getNano() == LocalDateTime.now(zIDToronto).getNano());
}
测试涉及 race condition 并通过(有时)因为计时和添加语句会改变计时并因此改变测试结果。
断言中检查的条件基本上是两个连续的日期时间的纳秒部分是否相等。
鉴于默认情况下 System.currentTimeMillis()
由 LocalDateTime.now
内部使用,并且它最多具有毫秒精度,如果第二个调用序列足够快以获取纳秒数,则检查将成功(这是导致调用 System.currentTimeMillis
在与第一个相同的毫秒内完成的实际调用顺序。
当你在实际断言之前调用用于获取纳秒值的函数时,相应的 类 被加载,相应方法的代码进入 CPU 缓存等。这使得第二对调用更快地获得纳秒数 运行。
终于有时间对此进行更深入的调查。
我开始试验,一段时间后发现,其实影响结果的不是System.out.println
的存在,而是你在它之前实例化了2个LocalDateTime
实例。
深入挖掘 LocalDateTime
的代码和 SystemClock
(它委托给它),我发现亚毫精度是通过调用本机调用 jdk.internal.misc.VM#getNanoTimeAdjustment
.
最后一次调用是 OS 特定的。我对它进行了一些试验,发现它的 return 值不是线性的,因为它在循环中被调用(假设我的循环 运行 相当规律)。
所以我决定 运行 一些代码来映射 returned 纳米值。
我做了这个示例代码:
Clock clock = Clock.systemDefaultZone();
int samples = 1_000;
LocalDateTime[] instants = new LocalDateTime[samples];
int k = 0;
for (int i = 0; i < samples; i++) {
instants[i] = LocalDateTime.now(clock);
for (int j = 0; j < 10000; j++) {
k = j % 2;
}
}
将值写入文件,然后将纳米差异与第一个值映射到图表中:
如您所见,此图(包含 1000 个值)间歇性跳跃。这显然部分是由于底层系统的精度限制。但令我震惊的是,前两个值始终不同。就好像在定期访问时 OS 系统开始缓存该值一段时间(可能是为了避免对系统资源造成压力)。
但结果似乎是您在第 3 次和第 4 次调用时设置了相同的值(除非已经过了足够的时间)。
这可以解释为什么您的测试通过了,而没有那些先前的实例化却失败了。
顺便说一句,对于单元测试,您不想依赖系统时钟。确保您的业务代码从注入的 Clock 实例获取时间。然后您可以为测试注入一个自定义时钟,并测试您的代码是否会 运行 在 DST 转换日期或闰日而无需等待几个月。
出于某种原因,在 class 的一节课中测试此方法时,我们发现了一个我们无法理解的问题。出于某种原因写 System.out.println();
时它通过了吗?。有人可以解释为什么会这样吗?
public class Zones {
public ZoneId getZoneId(String input) {
if (input.equalsIgnoreCase("Stockholm")) {
return ZoneId.of("Europe/Stockholm");
}
else if (input.equalsIgnoreCase("Shanghai")) {
return ZoneId.of("Asia/Shanghai");
} else if (input.equalsIgnoreCase("Toronto")) {
return ZoneId.of("America/Toronto");
}
else if (input.equalsIgnoreCase("Hamburg")) {
return ZoneId.of("Europe/Berlin");
}
else return null;
}
public LocalDateTime getZoneTime(ZoneId zoneId) {
LocalDateTime lt = LocalDateTime.now(zoneId);
return lt;
}
}
private Zones z = new Zones();
@Test
public void getZoneTimeTest () {
System.out.println(z.getZoneTime(zIDToronto).getNano() );
System.out.println(LocalDateTime.now(zIDToronto).getNano() );
assertTrue(z.getZoneTime(zIDToronto).getNano() == LocalDateTime.now(zIDToronto).getNano());
}
测试涉及 race condition 并通过(有时)因为计时和添加语句会改变计时并因此改变测试结果。
断言中检查的条件基本上是两个连续的日期时间的纳秒部分是否相等。
鉴于默认情况下 System.currentTimeMillis()
由 LocalDateTime.now
内部使用,并且它最多具有毫秒精度,如果第二个调用序列足够快以获取纳秒数,则检查将成功(这是导致调用 System.currentTimeMillis
在与第一个相同的毫秒内完成的实际调用顺序。
当你在实际断言之前调用用于获取纳秒值的函数时,相应的 类 被加载,相应方法的代码进入 CPU 缓存等。这使得第二对调用更快地获得纳秒数 运行。
终于有时间对此进行更深入的调查。
我开始试验,一段时间后发现,其实影响结果的不是System.out.println
的存在,而是你在它之前实例化了2个LocalDateTime
实例。
深入挖掘 LocalDateTime
的代码和 SystemClock
(它委托给它),我发现亚毫精度是通过调用本机调用 jdk.internal.misc.VM#getNanoTimeAdjustment
.
最后一次调用是 OS 特定的。我对它进行了一些试验,发现它的 return 值不是线性的,因为它在循环中被调用(假设我的循环 运行 相当规律)。
所以我决定 运行 一些代码来映射 returned 纳米值。
我做了这个示例代码:
Clock clock = Clock.systemDefaultZone();
int samples = 1_000;
LocalDateTime[] instants = new LocalDateTime[samples];
int k = 0;
for (int i = 0; i < samples; i++) {
instants[i] = LocalDateTime.now(clock);
for (int j = 0; j < 10000; j++) {
k = j % 2;
}
}
将值写入文件,然后将纳米差异与第一个值映射到图表中:
如您所见,此图(包含 1000 个值)间歇性跳跃。这显然部分是由于底层系统的精度限制。但令我震惊的是,前两个值始终不同。就好像在定期访问时 OS 系统开始缓存该值一段时间(可能是为了避免对系统资源造成压力)。
但结果似乎是您在第 3 次和第 4 次调用时设置了相同的值(除非已经过了足够的时间)。
这可以解释为什么您的测试通过了,而没有那些先前的实例化却失败了。
顺便说一句,对于单元测试,您不想依赖系统时钟。确保您的业务代码从注入的 Clock 实例获取时间。然后您可以为测试注入一个自定义时钟,并测试您的代码是否会 运行 在 DST 转换日期或闰日而无需等待几个月。