enhance serverside uml
This commit is contained in:
		
							parent
							
								
									f0c67b8bc4
								
							
						
					
					
						commit
						8c95d2f24f
					
				
					 2 changed files with 201 additions and 29 deletions
				
			
		| 
						 | 
					@ -6,22 +6,31 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```mermaid
 | 
					```mermaid
 | 
				
			||||||
classDiagram
 | 
					classDiagram
 | 
				
			||||||
class PermissionDescriptor {
 | 
					class IPermissionDescriptor {
 | 
				
			||||||
    +canRead(path: string): boolean
 | 
					    +canRead(path: string): boolean
 | 
				
			||||||
    +canWrite(path: string): boolean
 | 
					    +canWrite(path: string): boolean
 | 
				
			||||||
    +canCustom(path: string, options: any): boolean
 | 
					    +canCustom(path: string, options: any): boolean
 | 
				
			||||||
 | 
					    +toJSON(): any
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
<<Interface>> PermissionDescriptor
 | 
					<<Interface>> IPermissionDescriptor
 | 
				
			||||||
PermissionDescriptor <|.. PermissionImpl
 | 
					IPermissionDescriptor <|.. AdminPermission
 | 
				
			||||||
 | 
					class AdminPermission {
 | 
				
			||||||
 | 
					    +canRead(): boolean
 | 
				
			||||||
 | 
					    +canWrite(): boolean
 | 
				
			||||||
 | 
					    +canCustom(): boolean
 | 
				
			||||||
 | 
					    +toJSON(): Inline
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					IPermissionDescriptor <|.. PermissionImpl
 | 
				
			||||||
class PermissionImpl {
 | 
					class PermissionImpl {
 | 
				
			||||||
    +basePath: string
 | 
					    +basePath: string
 | 
				
			||||||
    +writable: boolean
 | 
					    +writable: boolean
 | 
				
			||||||
 | 
					    +toJSON(): Inline
 | 
				
			||||||
    +canRead(path: string): boolean
 | 
					    +canRead(path: string): boolean
 | 
				
			||||||
    +canWrite(path: string): boolean
 | 
					    +canWrite(path: string): boolean
 | 
				
			||||||
    +canCustom(_path: string, _options: any): boolean
 | 
					    +canCustom(_path: string, _options: any): boolean
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
SessionStore o-- UserSession
 | 
					SessionStore o-- UserSession
 | 
				
			||||||
UserSession *-- PermissionDescriptor
 | 
					UserSession *-- IUser
 | 
				
			||||||
class SessionStore~T~ {
 | 
					class SessionStore~T~ {
 | 
				
			||||||
    +sessions: Record<string, T>
 | 
					    +sessions: Record<string, T>
 | 
				
			||||||
    +get(id: string): T
 | 
					    +get(id: string): T
 | 
				
			||||||
| 
						 | 
					@ -34,9 +43,64 @@ class UserSession {
 | 
				
			||||||
    +id: string
 | 
					    +id: string
 | 
				
			||||||
    +superuser: boolean
 | 
					    +superuser: boolean
 | 
				
			||||||
    +expiredAt: number
 | 
					    +expiredAt: number
 | 
				
			||||||
    +permissionSet: PermissionDescriptor
 | 
					    +permissionSet: IPermissionDescriptor
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
<<Interface>> UserSession
 | 
					<<Interface>> UserSession
 | 
				
			||||||
 | 
					IPermissionDescriptor <|.. IUser
 | 
				
			||||||
 | 
					class IUser {
 | 
				
			||||||
 | 
					    +id: string
 | 
				
			||||||
 | 
					    +expiredAt: number
 | 
				
			||||||
 | 
					    +basepath: string
 | 
				
			||||||
 | 
					    +joinPath(path: string): string
 | 
				
			||||||
 | 
					    +relativePath(path: string): string
 | 
				
			||||||
 | 
					    +setExpired(seconds: number): void
 | 
				
			||||||
 | 
					    +isExpired(): boolean
 | 
				
			||||||
 | 
					    +toJSON(): any
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					<<Interface>> IUser
 | 
				
			||||||
 | 
					IUser <|.. UserSessionImpl
 | 
				
			||||||
 | 
					class UserSessionImpl {
 | 
				
			||||||
 | 
					    +id: string
 | 
				
			||||||
 | 
					    +expiredAt: number
 | 
				
			||||||
 | 
					    +permissionSet: IPermissionDescriptor
 | 
				
			||||||
 | 
					    +basepath: string
 | 
				
			||||||
 | 
					    +toJSON(): UserSessionImplJSON
 | 
				
			||||||
 | 
					    +joinPath(path: string): string
 | 
				
			||||||
 | 
					    +relativePath(path: string): string
 | 
				
			||||||
 | 
					    +setExpired(seconds: number): void
 | 
				
			||||||
 | 
					    +isExpired(): boolean
 | 
				
			||||||
 | 
					    +canRead(path: string): boolean
 | 
				
			||||||
 | 
					    +canWrite(path: string): boolean
 | 
				
			||||||
 | 
					    +canCustom(path: string, options: any): boolean
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					```mermaid
 | 
				
			||||||
 | 
					classDiagram
 | 
				
			||||||
 | 
					class DocumentContent {
 | 
				
			||||||
 | 
					    +chunks: RPC.Chunk[]
 | 
				
			||||||
 | 
					    +tags: string[]
 | 
				
			||||||
 | 
					    +version: number
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					<<Interface>> DocumentContent
 | 
				
			||||||
 | 
					class DocReadWriter {
 | 
				
			||||||
 | 
					    +read(path: string): Promise<DocumentContent>
 | 
				
			||||||
 | 
					    +save(path: string, doc: DocumentContent): Promise<void>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					<<Interface>> DocReadWriter
 | 
				
			||||||
 | 
					DocReadWriter <|.. MemoryDocReadWriterType
 | 
				
			||||||
 | 
					class MemoryDocReadWriterType {
 | 
				
			||||||
 | 
					    -store: Map<string, DocumentContent>
 | 
				
			||||||
 | 
					    +read(path: string): Promise<DocumentContent>
 | 
				
			||||||
 | 
					    +save(path: string, doc: DocumentContent): Promise<void>
 | 
				
			||||||
 | 
					    +clear(): void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					DocReadWriter <|.. DocFileReadWriterType
 | 
				
			||||||
 | 
					class DocFileReadWriterType {
 | 
				
			||||||
 | 
					    +rw: IReadWriter
 | 
				
			||||||
 | 
					    +read(path: string): Promise<DocumentContent>
 | 
				
			||||||
 | 
					    +save(path: string, doc: DocumentContent): Promise<void>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					DocumentContent ..> DocReadWriter
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
```mermaid
 | 
					```mermaid
 | 
				
			||||||
classDiagram
 | 
					classDiagram
 | 
				
			||||||
| 
						 | 
					@ -80,11 +144,20 @@ Router <|.. FsRouter
 | 
				
			||||||
classDiagram
 | 
					classDiagram
 | 
				
			||||||
class ResponseBuilder {
 | 
					class ResponseBuilder {
 | 
				
			||||||
    +status: Status
 | 
					    +status: Status
 | 
				
			||||||
    +headers: Record<string, string>
 | 
					    +headers: Headers
 | 
				
			||||||
    +body?: BodyInit
 | 
					    +body?: BodyInit
 | 
				
			||||||
 | 
					    +#response: Response
 | 
				
			||||||
 | 
					    +#resolved: boolean
 | 
				
			||||||
 | 
					    +resolved: boolean
 | 
				
			||||||
    +setStatus(status: Status): this
 | 
					    +setStatus(status: Status): this
 | 
				
			||||||
    +setHeader(key: string, value: string): this
 | 
					    +setHeader(key: string, value: string): this
 | 
				
			||||||
 | 
					    +setHeaders(headers: Record<string, string>): this
 | 
				
			||||||
    +setBody(body: BodyInit): this
 | 
					    +setBody(body: BodyInit): this
 | 
				
			||||||
 | 
					    +setResponse(response: Response, resolved?: boolean): this
 | 
				
			||||||
 | 
					    +setCors(origin: string, credentials: boolean): this
 | 
				
			||||||
 | 
					    +setCorsMethods(methods: string[]): this
 | 
				
			||||||
 | 
					    +setContentType(contentType: string): this
 | 
				
			||||||
 | 
					    +setJson(json: unknown): this
 | 
				
			||||||
    +redirect(location: string): this
 | 
					    +redirect(location: string): this
 | 
				
			||||||
    +build(): Response
 | 
					    +build(): Response
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -133,8 +206,10 @@ class ChunkMoveAction {
 | 
				
			||||||
classDiagram
 | 
					classDiagram
 | 
				
			||||||
class Participant {
 | 
					class Participant {
 | 
				
			||||||
    +id: string
 | 
					    +id: string
 | 
				
			||||||
    +user: UserSession
 | 
					    +user: IUser
 | 
				
			||||||
    +send(data: string): void
 | 
					    +send(data: string): void
 | 
				
			||||||
 | 
					    +sendNotification(notification: RPC.RPCNotification): void
 | 
				
			||||||
 | 
					    +responseWith(data: RPC.RPCResponse): void
 | 
				
			||||||
    +addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
 | 
					    +addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
 | 
				
			||||||
    +removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
 | 
					    +removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
 | 
				
			||||||
    +close(): void
 | 
					    +close(): void
 | 
				
			||||||
| 
						 | 
					@ -143,8 +218,10 @@ class Participant {
 | 
				
			||||||
Participant <|.. Connection
 | 
					Participant <|.. Connection
 | 
				
			||||||
class Connection {
 | 
					class Connection {
 | 
				
			||||||
    +id: string
 | 
					    +id: string
 | 
				
			||||||
    +user: UserSession
 | 
					    +user: IUser
 | 
				
			||||||
    +socket: WebSocket
 | 
					    +socket: WebSocket
 | 
				
			||||||
 | 
					    +sendNotification(notification: RPC.RPCNotification): void
 | 
				
			||||||
 | 
					    +responseWith(res: RPC.RPCResponse): void
 | 
				
			||||||
    +send(data: string): void
 | 
					    +send(data: string): void
 | 
				
			||||||
    +addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
 | 
					    +addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
 | 
				
			||||||
    +removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
 | 
					    +removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void
 | 
				
			||||||
| 
						 | 
					@ -156,33 +233,50 @@ class ParticipantList {
 | 
				
			||||||
    +get(id: string): any
 | 
					    +get(id: string): any
 | 
				
			||||||
    +remove(id: string): void
 | 
					    +remove(id: string): void
 | 
				
			||||||
    +unicast(id: string, message: string): void
 | 
					    +unicast(id: string, message: string): void
 | 
				
			||||||
    +broadcast(message: string): void
 | 
					    +unicastNotification(id: string, notification: RPC.RPCNotification): void
 | 
				
			||||||
 | 
					    +broadcastNotification(notification: RPC.RPCNotification): void
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
ParticipantList o-- Participant
 | 
					ParticipantList o-- Participant
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
```mermaid
 | 
					```mermaid
 | 
				
			||||||
classDiagram
 | 
					classDiagram
 | 
				
			||||||
DocumentObject <|.. FileDocumentObject
 | 
					class ISubscriptable {
 | 
				
			||||||
class FileDocumentObject {
 | 
					    +join(participant: Participant): void
 | 
				
			||||||
    +docPath: string
 | 
					    +leave(participant: Participant): void
 | 
				
			||||||
    +chunks: Chunk[]
 | 
					    +broadcastChunkMethod(method: ChunkNotificationParam, updatedAt: number, exclude?: Participant): void
 | 
				
			||||||
    +tags: string[]
 | 
					    +participantsCount: number
 | 
				
			||||||
    +updatedAt: number
 | 
					    +participants: Participant[]
 | 
				
			||||||
    +tagsUpdatedAt: number
 | 
					 | 
				
			||||||
    +open(): Promise<void>
 | 
					 | 
				
			||||||
    +parse(content: unknown[]): void
 | 
					 | 
				
			||||||
    +save(): Promise<void>
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
FileDocumentObject <|-- ActiveDocumentObject
 | 
					<<Interface>> ISubscriptable
 | 
				
			||||||
 | 
					ISubscriptable <|.. ActiveDocumentObject
 | 
				
			||||||
class ActiveDocumentObject {
 | 
					class ActiveDocumentObject {
 | 
				
			||||||
    +conns: Set<Participant>
 | 
					    -conns: RefCountSet<Participant>
 | 
				
			||||||
 | 
					    -disposeHandlers: (() => void)[]
 | 
				
			||||||
    +history: DocHistory[]
 | 
					    +history: DocHistory[]
 | 
				
			||||||
    +maxHistory: number
 | 
					    +maxHistory: number
 | 
				
			||||||
 | 
					    +docPath: string
 | 
				
			||||||
 | 
					    +chunks: RPC.Chunk[]
 | 
				
			||||||
 | 
					    +updatedAt: number
 | 
				
			||||||
 | 
					    +seq: number
 | 
				
			||||||
 | 
					    +#tags: string[]
 | 
				
			||||||
 | 
					    +tagsUpdatedAt: number
 | 
				
			||||||
 | 
					    +readWriter: DocReadWriter
 | 
				
			||||||
 | 
					    +dispose(): void
 | 
				
			||||||
 | 
					    +save(): Promise<void>
 | 
				
			||||||
 | 
					    +setTags(tags: string[]): void
 | 
				
			||||||
 | 
					    +tags: string[]
 | 
				
			||||||
 | 
					    +participantsCount: any
 | 
				
			||||||
 | 
					    +participants: Participant[]
 | 
				
			||||||
    +join(conn: Participant): void
 | 
					    +join(conn: Participant): void
 | 
				
			||||||
 | 
					    +joined(conn: Participant): boolean
 | 
				
			||||||
    +leave(conn: Participant): void
 | 
					    +leave(conn: Participant): void
 | 
				
			||||||
 | 
					    +open(): Promise<void>
 | 
				
			||||||
    +updateDocHistory(method: ChunkMethodHistory): void
 | 
					    +updateDocHistory(method: ChunkMethodHistory): void
 | 
				
			||||||
    +broadcastMethod(method: ChunkMethod, updatedAt: number, exclude?: Participant): void
 | 
					    +broadcastChunkMethod(method: ChunkNotificationParam, updatedAt: number, exclude?: Participant): void
 | 
				
			||||||
 | 
					    +broadcastTagsNotification(exclude?: Participant): void
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					IDisposable <|.. ActiveDocumentObject
 | 
				
			||||||
 | 
					DocumentObject <|.. ActiveDocumentObject
 | 
				
			||||||
class DocumentStore {
 | 
					class DocumentStore {
 | 
				
			||||||
    +documents: Inline
 | 
					    +documents: Inline
 | 
				
			||||||
    +open(conn: Participant, docPath: string): Promise<ActiveDocumentObject>
 | 
					    +open(conn: Participant, docPath: string): Promise<ActiveDocumentObject>
 | 
				
			||||||
| 
						 | 
					@ -193,16 +287,91 @@ DocumentStore o-- ActiveDocumentObject
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
```mermaid
 | 
					```mermaid
 | 
				
			||||||
classDiagram
 | 
					classDiagram
 | 
				
			||||||
class IFsWatcher{
 | 
					class IDisposable {
 | 
				
			||||||
    addEventListener(): void
 | 
					    +dispose(): void
 | 
				
			||||||
    onNofity(e: FileWatchEvent): void
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
<<interface>> IFsWatcher
 | 
					<<Interface>> IDisposable
 | 
				
			||||||
class FsWatcherImpl{
 | 
					IDisposable <|.. RefCountDisposable
 | 
				
			||||||
    onNotify(e: FileWatchEvent): void
 | 
					class RefCountDisposable {
 | 
				
			||||||
 | 
					    -refCount: number
 | 
				
			||||||
 | 
					    -handlers: (() => void)[]
 | 
				
			||||||
 | 
					    +dispose(): void
 | 
				
			||||||
 | 
					    +disposeForced(): void
 | 
				
			||||||
 | 
					    +addRef(): void
 | 
				
			||||||
 | 
					    +addDisposeHandler(handler: () => void): void
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					class RefCountSet~T~ {
 | 
				
			||||||
 | 
					    -items: Map<T, RefCountDisposable>
 | 
				
			||||||
 | 
					    +add(item: T, disposeHandler?: () => void): void
 | 
				
			||||||
 | 
					    +addDisposeHandler(item: T, handler: () => void): boolean
 | 
				
			||||||
 | 
					    +delete(item: T): void
 | 
				
			||||||
 | 
					    +deleteForced(item: T): void
 | 
				
			||||||
 | 
					    +clear(): void
 | 
				
			||||||
 | 
					    +size: number
 | 
				
			||||||
 | 
					    +has(item: T): boolean
 | 
				
			||||||
 | 
					    +values(): IterableIterator<T>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					RefCountDisposable --o RefCountSet~T~
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					```mermaid
 | 
				
			||||||
 | 
					classDiagram
 | 
				
			||||||
 | 
					Event <|-- FsWatcherEvent
 | 
				
			||||||
 | 
					class FsWatcherEvent {
 | 
				
			||||||
 | 
					    +paths: string[]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EventTarget <|-- FsWatcher
 | 
				
			||||||
 | 
					class FsWatcher {
 | 
				
			||||||
 | 
					    -path: string
 | 
				
			||||||
 | 
					    -watcher?: Deno.FsWatcher
 | 
				
			||||||
 | 
					    -filterFns: ((path: string, kind: FsWatchEventType) => boolean)[]
 | 
				
			||||||
 | 
					    +addFilter(fn: (path: string, kind: FsWatchEventType) => boolean): void
 | 
				
			||||||
 | 
					    +removeFilter(fn: (path: string, kind: FsWatchEventType) => boolean): boolean
 | 
				
			||||||
 | 
					    +startWatching(): void
 | 
				
			||||||
 | 
					    +stopWatching(): void
 | 
				
			||||||
 | 
					    +addEventListener(type: FsWatchEventType, handler: (e: FsWatcherEvent) => void): void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					```mermaid
 | 
				
			||||||
 | 
					classDiagram
 | 
				
			||||||
 | 
					class IReadWriter {
 | 
				
			||||||
 | 
					    +read(path: string): Promise<string>
 | 
				
			||||||
 | 
					    +write(path: string, content: string): Promise<void>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					<<Interface>> IReadWriter
 | 
				
			||||||
 | 
					class MemoryReadWriter {
 | 
				
			||||||
 | 
					    -data: Record<string, string>
 | 
				
			||||||
 | 
					    +read(path: string): Promise<string>
 | 
				
			||||||
 | 
					    +write(path: string, content: string): Promise<void>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					IReadWriter <|.. MemoryReadWriter
 | 
				
			||||||
 | 
					IReadWriter <|.. AtomicReadWriter
 | 
				
			||||||
 | 
					class AtomicReadWriter {
 | 
				
			||||||
 | 
					    +read(path: string): Promise<string>
 | 
				
			||||||
 | 
					    +write(path: string, content: string): Promise<void>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					IReadWriter <|.. WatchFilteredReadWriter
 | 
				
			||||||
 | 
					class WatchFilteredReadWriter {
 | 
				
			||||||
 | 
					    -raw: AtomicReadWriter
 | 
				
			||||||
 | 
					    -fsWatcher: FsWatcher
 | 
				
			||||||
 | 
					    +read(path: string): Promise<string>
 | 
				
			||||||
 | 
					    +write(path: string, content: string): Promise<void>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					IReadWriter <|.. QueueReadWriter
 | 
				
			||||||
 | 
					class QueueReadWriter {
 | 
				
			||||||
 | 
					    -queue: Command[]
 | 
				
			||||||
 | 
					    +started: boolean
 | 
				
			||||||
 | 
					    -waitedResolve: () => void
 | 
				
			||||||
 | 
					    +delayCount: number
 | 
				
			||||||
 | 
					    +baseReadWriter: IReadWriter
 | 
				
			||||||
 | 
					    +read(path: string): Promise<string>
 | 
				
			||||||
 | 
					    +write(path: string, content: string): Promise<void>
 | 
				
			||||||
 | 
					    +save(path: string, content: string): void
 | 
				
			||||||
 | 
					    +startTimer(): void
 | 
				
			||||||
 | 
					    +flush(): Promise<void>
 | 
				
			||||||
 | 
					    +wait(): Promise<void>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 5.1.2 Client Side UML
 | 
					### 5.1.2 Client Side UML
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```mermaid
 | 
					```mermaid
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,6 +47,9 @@
 | 
				
			||||||
      1. [Server Side UML](./architecture.md#511-server-side-uml)
 | 
					      1. [Server Side UML](./architecture.md#511-server-side-uml)
 | 
				
			||||||
      2. [Client Side UML](./architecture.md#512-client-side-uml)
 | 
					      2. [Client Side UML](./architecture.md#512-client-side-uml)
 | 
				
			||||||
   2. [의사코드(Pseudo Code)](./architecture.md#52-의사코드pseudo-code)
 | 
					   2. [의사코드(Pseudo Code)](./architecture.md#52-의사코드pseudo-code)
 | 
				
			||||||
 | 
					      1. [서버 RPC 메세지 처리](./architecture.md#521-서버-rpc-메세지-처리)
 | 
				
			||||||
 | 
					      2. [클라이언트의 메세지 처리 동기화](./architecture.md#522-클라이언트의-메세지-처리-동기화)
 | 
				
			||||||
 | 
					      3. [다른 작업들](./architecture.md#523-다른-작업들)
 | 
				
			||||||
6. [시험(Testing)](./testing.md)
 | 
					6. [시험(Testing)](./testing.md)
 | 
				
			||||||
   1. [유닛 테스트(Unit test)](./testing.md#61-유닛-테스트unit-test)
 | 
					   1. [유닛 테스트(Unit test)](./testing.md#61-유닛-테스트unit-test)
 | 
				
			||||||
   2. [기능 테스트(Functional Test)](./testing.md#62-기능-테스트functional-test)
 | 
					   2. [기능 테스트(Functional Test)](./testing.md#62-기능-테스트functional-test)
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue