写点什么

iOS 开发中的单元测试(三)——URLManager 中的测试用例解析

  • 2013-08-27
  • 本文字数:9646 字

    阅读完需:约 32 分钟

URLManager 是一个基于 UINavigationController 和 UIViewController,以 URL Scheme 为设计基础的导航控件,目的是实现 ViewController 的松耦合,不依赖。

准备框架,定义基类

首先按照之前的两篇文章介绍的方法导入单元测试框架和匹配引擎框架,建立好测试 Target,并配置编译选项。

定义测试用例基类: UMTestCase (代码 1),其他用例全部继承自 UMTestCase。

复制代码
#import <GHUnitIOS/GHTestCase.h>
@interface UMTestCase : GHTestCase
@end

代码 1,UMTestCase,用例基类

构建用例

URLManager 工具类( UMTools )测试用例( UMToolsTestCase )。UMTools 中扩展了 NSURL,NSString 和 UIView,方法涉及到给 URL 添加 QueryString 和从 QueryString 中读取参数,对字符串做子串判断,进行 URL 的编码和解码,对 UIView 的 x,y,width 和 height 的直接读写等。需要在用例中定义测试过程中会使用到属性(代码 2), 并在 setUpClass 中初始化他们(代码 3)。

复制代码
// 普通字符串,带有字母和数字
@property (strong, nonatomic) NSString *string;
// 普通字符串,仅带有字母
@property (strong, nonatomic) NSString *stringWithoutNumber;
// 将被做 URLEncode 的字符串,含有特殊字符和汉字
@property (strong, nonatomic) NSString *toBeEncode;
// 把 toBeEncode 编码后的串
@property (strong, nonatomic) NSString *encoded;
// 普通的 URL,带有 QueryString
@property (strong, nonatomic) NSURL *url;
// 去掉上边一个 URL 的 QueryString
@property (strong, nonatomic) NSURL *noQueryUrl;
// 一个普通的 UIView
@property (strong, nonatomic) UIView *view;

代码 2,定义属性

复制代码
(void)setUpClass
{
self.string = @"NSString For Test with a number 8848.";
self.stringWithoutNumber = @"NSString For Test.";
self.toBeEncode = @"~!@#$%^&*()_+=-[]{}:;\"'<>.,/?123qwe 汉字 ";
self.encoded = @"%7E%21%40%23%24%25%5E%26%2A%28%29_%2B%3D-%5B%5D%
7B%7D%3A%3B%22%27%3C%3E.%2C%2F%3F123qwe%E6%B1%89%E5%AD%97";
self.url = [NSURL URLWithString:@"http://example.com
/patha/pathb/?p2=v2&p1=v1"];
self.noQueryUrl = [NSURL URLWithString:@"http://example.com
/patha/pathb/"];
self.view = [[UIView alloc] initWithFrame:CGRectMake(10.0f,
10.0f, 100.0f, 100.f)];
}
{1}

代码 3,初始化属性

使用单元测试框架中的断言处理简单用例

单元测试是白盒测试,要做到路径覆盖(代码 4)。 对“ContainsString”的测试进行正向和反向两种情况(即 YES 和 NO 两种返回结果)。

复制代码
#pragma mark - UMString
- (void)testUMStringContainsString
{
NSString *p = @"For";
NSString *np = @"BAD";
GHAssertTrue([self.string containsString:p],
@"\"%@\" should contains \"%@\".",
self.string, p);
GHAssertFalse([self.string containsString:np],
@"\"%@\" should not contain \"%@\".",
self.string, p);

代码 4,字符串测试用例

同时单元测试又要对功能负责,因此在路径覆盖之外还要尽量照顾到完整的功能。例如,对 URLEncode 的测试(代码 5),要对尽量全面的特殊字符进行测试,而不是从源码实现中取出枚举的字符。

复制代码
(void)testUrlencode
{
GHAssertEqualStrings([self.toBeEncode urlencode], self.encoded,
@"URLEncode Error.",
self.toBeEncode, self.encoded);
GHAssertEqualStrings([self.encoded urldecode], self.toBeEncode,
@"URLDecode Error.",
self.encoded, self.toBeEncode);
}

代码 5,URLEncode 测试用例

在进行这个测试之前,urlencode 的实现忽视了对“~”的编码,正是由于单元测试用例所取的特殊字符是单独列举,并非从实现枚举中获取,检查出了这个错误。

引入匹配引擎,使用匹配引擎默认规则

前文提到过匹配引擎可以使测试用例中的断言更加丰富,URLManager 的用例中也使用了匹配引擎: OCHamcrest

在此前的介绍中提到,引入 OCHamcrest 可以通过定义“HC_SHORTHAND”来开启匹配引擎的简写模式。因为开启简写模式后匹配规则中的“containsString”规则和上述例子(代码 5)中的“containsString:”方法命名冲突,导致测试程序无法正常运行,所以这个工程直接使用了类似“HC_asserTaht”这样带有 HC 前缀的完整命名。

我建议使用匹配引擎的开发者谨慎开启简写功能,OCHamcrest 的匹配规则简写通常是很常见的单词,非常容易与工程中的类定义或方法定义重名。即使当下没有规则和方法名发生冲突,随着工程代码量的增加,一旦出现命名冲突的情况,重构的成本将非常高。

匹配引擎可以提供更丰富的断言,最简单的例如,URLManager 的 UMURL 扩展支持向一个 URL 上添加参数,对这个方法测试断言就用到了匹配某个字符串是否包含某子串的规则(代码 6)。

复制代码
#pragma mark - UMURL
- (void)testAddParams
{
NSURL *queryUrl = [self.noQueryUrl addParams:@{@"p1":@"v1",@"p2":@"v2"}];
HC_assertThat(queryUrl.absoluteString, HC_containsString(@"p1=v1"));
HC_assertThat(queryUrl.absoluteString, HC_containsString(@"p2=v2"));
}

代码 6,URL 参数测试用例

匹配规则中的陷阱

由于匹配规则的粒度较细,所以对于某些运行结果需要考虑到多种情况,否则正常的结果也可能会断言失败。

例如测试用例期望得到一个空容器(例如:NSArray),而 SDK 则认为这个容器已经没有存在的必要而释放了他,返回的是一个 nil。对 removeAllSubviews 的测试中,对一个 view 调用 removeAllSubviews 方法,期望 view.subviews 为空。在 SDK 6.x 甚至 SDK 7 DP1 之前,都是没问题的,但在 SDK 7 DP3 中,SDK 会把所有清空的容器和对象释放,以回收系统资源。在这种条件下 view.subviews 返回的就是 nil,如果只是做类似 HC_empty() 这样的匹配,断言会失败,所以在断言之前做一个 subviews 属性的空判断(代码 7)。

复制代码
(void)testRemoveAllSubviews
{
UIView *subViewA = [[UIView alloc] init];
UIView *subViewB = [[UIView alloc] init];
[self.view addSubview:subViewA];
[self.view addSubview:subViewB];
HC_assertThat(self.view.subviews, HC_containsInAnyOrder(subViewA, subViewB, nil));
[self.view removeAllSubviews];
if (nil != self.view.subviews) {
HC_assertThat(self.view.subviews, HC_empty());
}
}

代码 7,removeAllSubviews 用例

另外,在默认匹配规则中会有一些容易产生歧义的命名,以 collection 的 containsInAnyOrder 为例:匹配对象是一个 collection 对象(也就是遵循 NSFastEnumeration 协议的对象,NSArray 等),给出若干个匹配规则或元素。期待这个规则匹配该对象是否包含给出的若干元素,且不关心顺序。但在实际测试过程中会发现,这个规则要求给出的元素必须是该 collection 对象的完备集,也就是说要求给出的元素列表和要匹配的容器对象中的元素必须是相等的结合,但允许不关注顺序。

对 UMNavigationController 的测试中,需要判断增加一项 URL Mapping 是否生效,如果使用该匹配规则,就不能单纯判断 config 是否包含增量的 URL,要断言成功必须连同此前 config 属性初始化写入的值一起考虑,使用一个完整的元素集合进行匹配(代码 8)。

复制代码
(void)testAddConfig
{
[UMNavigationController setViewControllerName:@"ViewControllerA" forURL:@"<br></br>um://viewa2"];
NSMutableDictionary *config = [UMNavigationController config];
NSLog(@"%@", [config allKeys]);
HC_assertThat([config allKeys],
HC_containsInAnyOrder(HC_equalTo(@"um://viewa2"), HC_equalTo(@"<br></br>um://viewa"),
HC_equalTo(@"um://viewb"), nil));
GHAssertEqualStrings(config[@"um://viewa2"], @"ViewControllerA",
@"config set error.");
}

代码 8,AddConfig 用例

自建匹配规则

上述例子表明匹配规则往往无法恰好满足测试需求,需要对默认规则进行升级。

升级一个匹配规则,首先阅读 OCHamcrest 默认规则源码,找到无法满足需求的代码。上述 HC_containsInAnyOrder 的例子中,个性需求是某个 collection 是否包含某几个元素(而非完整集合),而默认规则只能匹配完整集合。阅读源码(代码 9)可以发现,在 maches:describingMismatchTo: 函数中,对规则对象的 collection 属性(要进行匹配的容器对象)进行遍历,并逐个调用 matches: 方法。matches: 方法中针对每个 collection 属性中的元素遍历匹配规则集合(matchers),并从规则集合(matchers)中移除匹配成功的规则。当给出的规则集合(matchers)全部成功匹配过之后,matchers 属性已经为空。若此时对 collection 属性的遍历继续进行,matches: 方法就不会进入匹配逻辑,直接跳出循环返回 NO,导致匹配失败。

复制代码
(BOOL)matches:(id)item
{
NSUInteger index = 0;
for (id<HCMatcher> matcher in matchers)
{
if ([matcher matches:item])
{
[matchers removeObjectAtIndex:index];
return YES;
}
++index;
}
[[mismatchDescription appendText:@"not matched: "] appendDescriptionOf:item];
return NO;
}
- (BOOL)matches:(id)collection describingMismatchTo:(id<HCDescription>)<br></br>mismatchDescription
{
if (![collection conformsToProtocol:@protocol(NSFastEnumeration)])
{
[super describeMismatchOf:collection to:mismatchDescription];
return NO;
}
HCMatchingInAnyOrder *matchSequence =
[[HCMatchingInAnyOrder alloc] initWithMatchers:matchers
mismatchDescription:mismatchDescription];
for (id item in collection)
if (![matchSequence matches:item])
return NO;
return [matchSequence isFinishedWith:collection];
}

代码 9,HC_containsInAnyOrder 规则中的两个核心方法

我们的需求是,当匹配规则列表全部成功匹配之后就是此次匹配成功的标志。所以需要修改 matches: 方法中的匹配逻辑,当匹配列表为空则返回 YES。

升级方案是继承 HCIsCollectionContainingInAnyOrder 创建一个新的匹配规则类 HCIsCollectionHavingInAnyOrder;重新定义匹配规则 HC_hasInAnyOrder;重写调用 matches: 方法的 matches:describingMismatchTo: 方法(代码 10);更新的核心是定义一个 HCMatchingInAnyOrderEx 类,按照个性需求定义 matches: 方法(代码 11)。使用这个修改过的匹配规则就可以判断一个 Collection 是否包含某个几个元素了。

复制代码
@implementation HCIsCollectionHavingInAnyOrder
- (BOOL)matches:(id)collection describingMismatchTo:(id<HCDescription>)<br></br>mismatchDescription
{
if (![collection conformsToProtocol:@protocol(NSFastEnumeration)])
{
[super describeMismatchOf:collection to:mismatchDescription];
return NO;
}
HCMatchingInAnyOrderEx *matchSequence =
[[HCMatchingInAnyOrderEx alloc] initWithMatchers:matchers
mismatchDescription:mismatchDescription];
for (id item in collection)
if (![matchSequence matches:item])
return NO;
return [matchSequence isFinishedWith:collection];
}
@end
id<HCMatcher> HC_hasInAnyOrder(id itemMatch, ...)
{
NSMutableArray *matchers = [NSMutableArray arrayWithObject:HCWrapInMatcher<br></br>(itemMatch)];
va_list args;
va_start(args, itemMatch);
itemMatch = va_arg(args, id);
while (itemMatch != nil)
{
[matchers addObject:HCWrapInMatcher(itemMatch)];
itemMatch = va_arg(args, id);
}
va_end(args);
return [HCIsCollectionHavingInAnyOrder isCollectionContainingInAnyOrder:matchers];
}

代码 10,HCIsCollectionHavingInAnyOrder 实现

复制代码
(BOOL)matches:(id)item
{
NSUInteger index = 0;
BOOL matched = (0 >= [self.matchers count]);
for (id<HCMatcher> matcher in self.matchers)
{
if ([matcher matches:item]) {
[self.matchers removeObjectAtIndex:index];
matched = YES;
return YES;
}
++index;
}
return matched;
}

代码 11,更新过的 matches: 方法

复制代码
(void)testAddConfig
{
[UMNavigationController setViewControllerName:@"ViewControllerA" forURL:@"um://<br></br>viewa2"];
NSMutableDictionary *config = [UMNavigationController config];
HC_assertThat([config allKeys],
HC_hasInAnyOrder(HC_equalTo(@"um://viewa2"), nil));
GHAssertEqualStrings(config[@"um://viewa2"], @"ViewControllerA",
@"config set error.");
}

代码 12,使用新规则的测试用例

另一个方面,在测试过程中会出现各种逻辑,有时默认规则根本无法覆盖,需要完全自建规则。例如对 CGPoint 和 CGSize 的相等匹配,如代码 13 中对 UMView 的 size 和 origin 方法测试。OCHamcrest 的默认规则中根本没有提供任何针对 CGPoint 和 CGSize 两个结构体的匹配规则,所以要完成这个测试就需要自己定义针对这两种数据结构的匹配规则。

复制代码
#pragma mark - UMView
HC_assertThat(NSStringFromCGSize(self.view.size),
HC_equalToSize(self.view.frame.size));
HC_assertThat(NSStringFromCGPoint(self.view.origin),
HC_equalToPoint(CGPointMake(self.view.frame.origin.x, self.<br></br>view.frame.origin.y)));

代码 13,UMView 测试用例片段

自定义匹配规则的详细说明可以参见上一篇《iOS 开发中的单元测试(二)》,本文只对开发自定义规则中遇到的问题和需要特殊处理的方面进行解释。

OCHamcrest 的匹配规则要求被匹配的必须是一个有强引用的对象,所以当被匹配的是一个 struct 结构(如 CGPoint)需要进行一次转换,如代码 14 中定义的这个规则扩展——OBJC_EXPORT id HC_equalToPoint(CGPoint point)。 在 CGPoint 相等匹配的规则中,需要先把 CGPoint 转为字符串后传入断言方法,规则会把这个字符串储存起来,并与后续给出的 CGPoint 进行比较。匹配引擎对传入的需要进行匹配的参数类型没做任何限制,所以规则可以直接传入 CGPoint。

开发自定义规则一般建议同时定义 SHORTHAND,即使当前单元测试中不会用到(例如本文中的测试),但这个规则被其他复用的时候,可能会用到 SHORTHAND 命名。

复制代码
#import <OCHamcrestIOS/HCBaseMatcher.h>
OBJC_EXPORT id<HCMatcher> HC_equalToPoint(CGPoint point);
#ifdef HC_SHORTHAND
#define equalToPoint HC_equalToPoint
#endif
@interface HCIsEqualToPoint : HCBaseMatcher
+ (id)equalToPoint:(CGPoint)point;
- (id)initWithPoint:(CGPoint)point;
@property (nonatomic, assign) CGFloat x;
@property (nonatomic, assign) CGFloat y;
@end

代码 14,扩展匹配规则 HC_equalToPoint 定义

在匹配规则的过程中,有一个点需要特别注意,即对匹配对象类型和完整性的判断。往往开发者把注意力都放在对对象值的匹配上,而忽略了类型和完整性这类判断,最终导致整个用例运行失败,但无法准确定位出错的位置。上面提到的对 subviews 是否为空的判断也是这样的一个例子。所以在自定义的匹配规则中就需要考虑到这方面的问题,如代码 15 的 matches: 方法中,先要对传入的泛型对象 item 校验是否为字符串,后再转化为 CGPoint 对象,并进行相应比对。示例中给出的是一种较简单的情况,在更复杂的情况下,除了对泛型对象的类进行校验,还要校验其是否响应某方法,属性类型,空判断,等。

复制代码
#import "HCIsEqualToPoint.h"
#import <OCHamcrestIOS/HCDescription.h>
id <HCMatcher> HC_equalToPoint(CGPoint point)
{
return [HCIsEqualToPoint equalToPoint:point];
}
@implementation HCIsEqualToPoint
+ (id)equalToPoint:(CGPoint)point
{
return [[self alloc] initWithPoint:point];
}
- (id)initWithPoint:(CGPoint)point
{
self = [super init];
if (self) {
self.x = point.x;
self.y = point.y;
}
return self;
}
- (BOOL)matches:(id)item
{
if (! [item isKindOfClass:[NSString class]]) {
return NO;
}
CGPoint point = CGPointFromString((NSString *)item);
return (point.x == self.x && point.y == self.y);
}
- (void)describeTo:(id<HCDescription>)description
{
[description appendText:@"Point not equaled."];
}
@end

代码 15,扩展匹配规则 HC_equalToPoint 实现

一个操作多个测试方法

以上提到的几个例子中所测试的都是非常简单的操作,所以一个测试方法覆盖了一个或多个操作,但对于较复杂的操作,往往需要多个测试方法,循序渐进的断言。例如测试通过 URL 生成 UMViewController 的用例,生成一个 UMViewController 实例由简单到复杂可以有三种简单方式:简单的 URL 生成,带参数的 URL 生成和带 Query 字典的 URL 生成,此外还有 URL 参数和 Query 字典共用的方式。所以对于这个操作至少需要使用 4 个测试方法(代码 16)分别进行测试。

复制代码
(void)testViewControllerForSimpleURL
{
self.viewControllerA = (ViewControllerA *)[self.navigator
viewControllerForURL:
[NSURL URLWithString:@"um://viewa"]
withQuery:nil];
HC_assertThat(self.viewControllerA, HC_instanceOf([UMViewController class]));
HC_assertThat(self.viewControllerA, HC_isA([ViewControllerA class]));
}
- (void)testViewControllerForURLWithArgs
{
self.viewControllerA = (ViewControllerA *)[self.navigator
viewControllerForURL:[NSURL URLWithString:@"um://viewa?<br></br>p1=v1&p2=v2"]
withQuery:nil];
HC_assertThat(self.viewControllerA, HC_instanceOf([UMViewController class]));
HC_assertThat(self.viewControllerA, HC_isA([ViewControllerA class]));
HC_assertThat([self.viewControllerA.params allKeys], HC_containsInAnyOrder<br></br>(@"p1", @"p2", nil));
GHAssertEqualStrings(self.viewControllerA.params[@"p1"], @"v1", @"param error.");
GHAssertEqualStrings(self.viewControllerA.params[@"p2"], @"v2", @"param error.");
}
- (void)testViewControllerWithQuery
{
self.viewControllerA = (ViewControllerA *)[self.navigator
viewControllerForURL:
[NSURL URLWithString:@"um://viewa"]
withQuery:@{@"k1":@"v1", @"k2":@"v2"}];
HC_assertThat([self.viewControllerA.query allKeys], HC_containsInAnyOrder<br></br>(@"k1", @"k2", nil));
GHAssertEqualStrings(self.viewControllerA.query[@"k1"], @"v1", @"param error.");
GHAssertEqualStrings(self.viewControllerA.query[@"k2"], @"v2", @"param error.");
}
- (void)testViewControllerForURLAndQuery
{
self.viewControllerA = (ViewControllerA *)[self.navigator
viewControllerForURL:
[NSURL URLWithString:@"um://viewa?p1=v1&p2=v2"]
withQuery:@{@"k1":@"v1", @"k2":@"v2"}];
HC_assertThat([self.viewControllerA.params allKeys], HC_containsInAnyOrder<br></br>(@"p1", @"p2", nil));
GHAssertEqualStrings(self.viewControllerA.params[@"p1"], @"v1", @"param error.");
GHAssertEqualStrings(self.viewControllerA.params[@"p2"], @"v2", @"param error.");
HC_assertThat([self.viewControllerA.query allKeys], HC_containsInAnyOrder<br></br>(@"k1", @"k2", nil));
GHAssertEqualStrings(self.viewControllerA.query[@"k1"], @"v1", @"param error.");
GHAssertEqualStrings(self.viewControllerA.query[@"k2"], @"v2", @"param error.");
}

代码 16,测试通过 URL 生成 UMViewController 的用例

一个测试方法多次断言

除了一个操作需要多个测试方法的情况,在同一个测试方法中也会有对一个结果进行多次断言的情况(上述用例代码 16 中已经是这种情况,一下用例更具代表性)。这种情况发生在操作结果较为复杂的情况下,例如生成一个 UMNavigationController(代码 17)就是这种情况:UMNavigationController 的初始化方法是带 RootViewController 参数的,所以初始化的实例除了判断其本身是否为 UINavigationController 的子类和 UMNavigationController 实例外,还要判断 rootViewController 的合法性,以及 viewControllers 数组的正确性。

复制代码
(void)testInitWihtRootViewControllerURL
{
UMNavigationController *navigator = [[UMNavigationController alloc]
initWithRootViewControllerURL:[NSURL URLWithString:@"um://viewb"]];
HC_assertThat(navigator, HC_instanceOf([UINavigationController class]));
HC_assertThat(navigator, HC_isA([UMNavigationController class]));
HC_assertThat(navigator.rootViewController,
HC_instanceOf([UMViewController class]));
HC_assertThat(navigator.rootViewController, HC_isA([ViewControllerB class]));
HC_assertThatInteger(navigator.viewControllers.count, HC_equalToInteger(1));
HC_assertThat(navigator.viewControllers,
HC_hasInAnyOrder(HC_instanceOf([UMViewController class]), nil));
HC_assertThat(navigator.viewControllers,
HC_hasInAnyOrder(HC_isA([ViewControllerB class]), nil));
HC_assertThat(navigator.viewControllers,
HC_hasInAnyOrder(HC_is(navigator.rootViewController), nil));
}

代码 17,测试生成 UMNavigationController 的用例

总结

本文一共取了 URLManager 中的 17 段代码片段作为例子,介绍了从利用测试框架提供的断言方法进行简单的测试,一直到使用自定义匹配引擎规则创建较复杂测试用例,并且提到了部分测试引擎和匹配引擎使用过程中会遇到的陷阱。旨在推动开发者能够在开发过程中更简单高效的使用单元测试,为提升代码质量增加一份保障。读者可以在 URLManager 的工程中阅读更多的测试用例代码。

此前预告单元测试系列文章共 3 篇,由于此前的 WWDC 2013 上新版本的 XCode 在单元测试方面做了比较大的更新,所以我会在两周内再为大家介绍一下有关新版本 XCode 的单元测试和持续集成的相关内容,作为单元测试系列文章的番外篇,谢谢大家。


感谢李永伦对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2013-08-27 06:403880

评论

发布
暂无评论
发现更多内容

区块链药品溯源解决方案-区块链技术监管医药溯源

13530558032

Flutter 2 来了

SamGo

flutter

Elasticsearch Index Types and Mappings

escray

elastic 七日更 28天写作 死磕Elasticsearch 60天通过Elastic认证考试 3月日更

简易项目搭建 Models层封装

happlyfox

学习 28天写作 3月日更

Oracle table()函数的合理运用,提高工作效率

xiezhr

oracle table() Oracle函数 3月日更

饿了么刚给我确认了p7的职位,对自己的经历,做一个面试总结。

Java架构之路

Java 程序员 架构 面试 编程语言

硬件设计必备,电子元器件高清矢量图

不脱发的程序猿

硬件产品 28天写作 硬件设计 电子元器件矢量图 3月日更

话题讨论 | 又一家大厂进入汽车领域,"百车大战"即将来临?

程序员架构进阶

话题讨论 七日更 28天写作 话题王者 3月日更

Redis 与 I/O 多路复用模型

大海

redis

程序员必须知道的数据结构:HashMap 与 LinkedHashMap

老王说编程

Java 数据结构 hashmap

正则表达式.01 - 元字符

insight

正则表达式 3月日更

微信团队分享:微信直播聊天室单房间1500万在线的消息架构演进之路

JackJiang

微信 架构设计 即时通讯

mock 请求分发

blueju

JavaScript React Mock umi umijs

程序员必须知道的数据结构:队列与栈

老王说编程

数据结构 队列

金三银四程序员面试必备:2021最新 最全面Java复习路线!已收录GitHub

比伯

Java 编程 程序员 架构 面试

【回溯算法】借助最后一道「组合总和」问题来总结一下回溯算法 ...

宫水三叶的刷题日记

面试 LeetCode 数据结构与算法

恋物志(二):独居者的智能生活指南

脑极体

萌新不看会后悔的C++基本类型总结(二)

花狗Fdog

两会热词“区块链”,打开传统溯源的一扇大门!

源中瑞-龙先生

区块链 两会

【金三银四】这才是打开Java面试的正确方式,吃透这份【Java面试手册】offer稳了

Java 编程 面试

力扣(LeetCode)刷题,简单题(第21期)

不脱发的程序猿

面试 LeetCode 28天写作 算法面经 3月日更

LeetCode题解:714. 买卖股票的最佳时机含手续费,动态规划,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

程序员必须知道的数据结构:线性表与链表

老王说编程

Java 链表 线性表

智慧党建系统开发,智慧组工平台建设

13530558032

《不看后悔》38个JVM精选问答,让你变成专家

Java 架构 面试 JVM虚拟机原理

总结近期腾讯+阿里+百度Java岗高频面试题,提问率高达98%,看到这篇文章基本offer稳了

Java架构之路

Java 程序员 架构 面试 编程语言

阿里面经最新分享:Java面试指南/成长笔记(金三银四程序员必备)

比伯

Java 编程 程序员 架构 面试

Wireshark数据包分析学习笔记Day3

穿过生命散发芬芳

Wireshark 数据包分析 3月日更

简单工厂模式、工厂模式、抽象工厂模式比较

良知犹存

设计模式

用c++创作一个简单小游戏

张鹤羽

28天写作 3月日更

开源镜像仓库Harbor的镜像安全

运维研习社

Docker Harbor 漏洞扫描 镜像安全 私有仓库

iOS开发中的单元测试(三)——URLManager中的测试用例解析_软件工程_高嘉峻_InfoQ精选文章