{"id":313195,"date":"2020-11-16T09:00:25","date_gmt":"2020-11-16T09:00:25","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=313195"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=313195","title":{"rendered":"Composable Architecture \u2014 \u0441\u0432\u0435\u0436\u0438\u0439 \u0432\u0437\u0433\u043b\u044f\u0434 \u043d\u0430 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0422\u0435\u0441\u0442\u044b"},"content":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\">\n<p>\u0421\u0431\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0434\u043b\u0435\u0432\u0430\u0435\u0442 \u0436\u0438\u0437\u043d\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u0443 \u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c. <\/p>\n<p>  <\/p>\n<h1 id=\"v-proshloy-serii\">\u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0435\u0440\u0438\u0438<\/h1>\n<p>  <\/p>\n<p><a href=\"https:\/\/habr.com\/ru\/post\/526782\/\">\u0427\u0430\u0441\u0442\u044c 1 \u2014 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b \u0438 \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 Composable Architecture<\/a><\/p>\n<p>  <\/p>\n<h1 id=\"testiruemyy-kod\">\u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u0439 \u043a\u043e\u0434<\/h1>\n<p>  <\/p>\n<p>\u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c \u0432\u044b\u043f\u0443\u0441\u043a\u0435 \u0431\u044b\u043b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d \u043a\u0430\u0440\u043a\u0430\u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u043a\u0443\u043f\u043e\u043a \u043d\u0430 <strong>Composable Architecture<\/strong>. \u041f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c \u043a\u0430\u043a \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u043d\u0430\u0440\u0430\u0449\u0438\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u2014 \u043f\u043e\u043a\u0440\u044b\u0442\u044c \u043a\u043e\u0434 \u0442\u0435\u0441\u0442\u0430\u043c\u0438. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0434\u0432\u0430 \u0432\u0438\u0434\u0430 \u0442\u0435\u0441\u0442\u043e\u0432: unit \u0442\u0435\u0441\u0442\u044b \u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0438 snapshot \u0442\u0435\u0441\u0442\u044b \u043d\u0430 UI. <\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<h2 id=\"chto-my-imeem\">\u0427\u0442\u043e \u043c\u044b \u0438\u043c\u0435\u0435\u043c?<\/h2>\n<p>  <\/p>\n<p>\u0415\u0449\u0435 \u0440\u0430\u0437 \u0432\u0437\u0433\u043b\u044f\u043d\u0435\u043c \u043d\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435: <\/p>\n<p>  <\/p>\n<ul>\n<li>\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u044d\u043a\u0440\u0430\u043d\u0430 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u043f\u0438\u0441\u043a\u043e\u043c \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432;<\/li>\n<li>\u0434\u0432\u0430 \u0432\u0438\u0434\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439: \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u0440\u043e\u0434\u0443\u043a\u0442 \u043f\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0443 \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439;<\/li>\n<li>\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0438 \u043c\u0435\u043d\u044f\u044e\u0449\u0438\u0439 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u2014 \u044f\u0440\u043a\u0438\u0439 \u043f\u0440\u0435\u0442\u0435\u043d\u0434\u0435\u043d\u0442 \u0434\u043b\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u043e\u0432.<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"swift\">struct ShoppingListState: Equatable {     var products: [Product] = [] }  enum ShoppingListAction {     case productAction(Int, ProductAction)     case addProduct }  let shoppingListReducer: Reducer&lt;ShoppingListState, ShoppingListAction, ShoppingListEnviroment&gt; = .combine(     productReducer.forEach(         state: \\.products,         action: \/ShoppingListAction.productAction,         environment: { _ in ProductEnviroment() }     ),     Reducer { state, action, env in         switch action {         case .addProduct:             state.products.insert(                 Product(id: UUID(), name: &quot;&quot;, isInBox: false),                 at: 0             )             return .none         case .productAction:             return .none         }     } )<\/code><\/pre>\n<p>  <\/p>\n<h2 id=\"tipy-testov\">\u0422\u0438\u043f\u044b \u0442\u0435\u0441\u0442\u043e\u0432<\/h2>\n<p>  <\/p>\n<blockquote><p>\u041a\u0430\u043a \u043f\u043e\u043d\u044f\u0442\u044c \u0447\u0442\u043e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u043d\u0435 \u043e\u0447\u0435\u043d\u044c? \u041b\u0435\u0433\u043a\u043e, \u0435\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u043a\u0440\u044b\u0442\u044c \u0435\u0435 \u043d\u0430 100% \u0442\u0435\u0441\u0442\u0430\u043c\u0438 (Vladislav Zhukov)<\/p><\/blockquote>\n<p>\u041d\u0435 \u0432\u0441\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b \u0447\u0435\u0442\u043a\u043e \u0440\u0435\u0433\u043b\u0430\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u044e\u0442 \u043f\u043e\u0434\u0445\u043e\u0434\u044b \u043a \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e. \u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043a\u0430\u043a \u044d\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0443 \u0440\u0435\u0448\u0430\u0435\u0442 Composable Arhitecutre.<\/p>\n<p>  <\/p>\n<h2 id=\"unit-testy\">Unit \u0442\u0435\u0441\u0442\u044b<\/h2>\n<p>  <\/p>\n<p>\u041e\u0434\u043d\u0430 \u0438\u0437 \u043f\u0440\u0438\u0447\u0438\u043d \u043f\u043e\u043b\u044e\u0431\u0438\u0442\u044c Composable Arhitecutre \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u0445\u043e\u0434 \u043a \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044e unit \u0442\u0435\u0441\u0442\u043e\u0432. <\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/h8\/mp\/0r\/h8mp0roxaigutg_fzp41o2csuzw.png\" alt=\"image alt\" align=\"right\" width=\"230\">  <\/p>\n<p>\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u2014 recuder&#8217;\u0430 \u2014 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u0448\u0430\u0433\u043e\u0432: <strong>send(Action)<\/strong> \u0438 <strong>receive(Action)<\/strong>. \u041d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u044d\u0442\u0430\u043f\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c \u0434\u043e\u043b\u0436\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c. <\/p>\n<p>  <\/p>\n<p><strong>Send(Action)<\/strong> \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u043c\u0438\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. <\/p>\n<p>  <\/p>\n<p><strong>Receive(Action)<\/strong> \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u043d\u0430 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c \u0448\u0430\u0433\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u043b\u0441\u044f \u044d\u0444\u0444\u0435\u043a\u0442 \u0438 \u0432\u0435\u0440\u043d\u0443\u043b \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u2014 action.<\/p>\n<p>  <\/p>\n<p>\u0412 \u043a\u043e\u043d\u0446\u0435 \u0442\u0435\u0441\u0442\u0430 \u0438\u043b\u0438 \u043f\u043e \u0445\u043e\u0434\u0443 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u0432 \u0431\u043b\u043e\u043a\u0435 .do {} \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043a \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c.<\/p>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u043d\u0430\u0448 \u0442\u0435\u0441\u0442 \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430.<\/p>\n<p>  <\/p>\n<pre><code class=\"swift\">func testAddProduct() {     \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u0441\u0442\u043e\u0440     let store = TestStore(         initialState: ShoppingListState(             products: []         ),         reducer: shoppingListReducer,         environment: ShoppingListEnviroment()     )     \/\/ \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b     store.assert(         \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430         .send(.addProduct) { state in             \/\/ \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b             state.products = [                 Product(                     id: UUID(),                     name: &quot;&quot;,                     isInBox: false                 )             ]         }     ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0432\u043e\u0435 \u043d\u0430 \u0447\u0442\u043e \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u043e\u0442 \u0441\u043b\u043e\u044f \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f. <\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/x9\/le\/t_\/x9let_k6ycuetutzko_fot761aa.png\" alt=\"image alt\" align=\"right\" width=\"400\">  <\/p>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0442\u0435\u0441\u0442 \u0438 \u043b\u043e\u0432\u0438\u043c \u0444\u0435\u0439\u043b<\/p>\n<p>  <\/p>\n<p>\u0414\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0432\u043d\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435 \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043d\u0430\u043c \u043e \u043d\u0435\u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0438 \u043f\u0440\u0438\u0441\u0432\u043e\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430 \u0441 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u043c:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/1p\/bf\/ei\/1pbfeitricsu0cx6bghv2-chpco.png\">  <\/p>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0432 \u0447\u0435\u043c \u0434\u0435\u043b\u043e, \u0432\u0432\u0435\u0434\u0435\u043c \u043f\u043e\u043d\u044f\u0442\u0438\u0435 <strong>\u0447\u0438\u0441\u0442\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f<\/strong>. <\/p>\n<p>  <\/p>\n<h3 id=\"reducer--chistaya-funkciya\">Reducer \u2014 \u0447\u0438\u0441\u0442\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f<\/h3>\n<p>  <\/p>\n<p>\u0427\u0442\u043e \u0436\u0435 \u0442\u0430\u043a\u043e\u0435 \u0447\u0438\u0441\u0442\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f?<\/p>\n<p>  <\/p>\n<p><strong>\u00ab\u0427\u0438\u0441\u0442\u044b\u0435\u00bb \u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/strong> \u2014 \u044d\u0442\u043e \u043b\u044e\u0431\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u044b \u0438\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0438\u0437 \u0438\u0445 \u0432\u0445\u043e\u0434\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043d\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0442 \u043f\u043e\u0431\u043e\u0447\u043d\u044b\u0445 \u044d\u0444\u0444\u0435\u043a\u0442\u043e\u0432 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438. <\/p>\n<p>  <\/p>\n<p>\u0412 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438, \u0432\u043d\u0443\u0442\u0440\u0438 \u043d\u0430\u0448\u0435\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f UUID \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430. \u0422\u0430\u043a\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0431\u043e\u0447\u043d\u044b\u043c \u044d\u0444\u0444\u0435\u043a\u0442\u043e\u043c, \u0430 \u043d\u0430\u0448\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f &quot;\u0433\u0440\u044f\u0437\u043d\u043e\u0439&quot;.<\/p>\n<p>  <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u044d\u0442\u043e \u0438\u0441\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c UUID \u0447\u0435\u0440\u0435\u0437 \u0441\u0435\u0440\u0432\u0438\u0441. \u0412 Composable Architecture \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f (<strong>Environment<\/strong>). <\/p>\n<p>  <\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u043d\u0430\u0448 <strong>ShoppingListEnviroment<\/strong> \u0441\u0435\u0440\u0432\u0438\u0441 (\u0444\u0443\u043d\u043a\u0446\u0438\u044e) \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 UUID.<\/p>\n<p>  <\/p>\n<pre><code class=\"swift\">struct ShoppingListEnviroment {     var uuidGenerator: () -&gt; UUID }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0435\u0435 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"swift\">Reducer { state, action, env in     switch action {     case .addProduct:         state.products.insert(             Product(                 id: env.uuidGenerator(),                 name: &quot;&quot;,                 isInBox: false             ),             at: 0         )         return .none     ...     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0447\u0438\u0441\u0442\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u043e\u0436\u043d\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c. \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044f\u0441\u044c \u043a \u043d\u0430\u0448\u0435\u043c\u0443 \u0442\u0435\u0441\u0442\u0443 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<p>  <\/p>\n<pre><code class=\"swift\">func testAddProduct() {     let store = TestStore(         initialState: ShoppingListState(),         reducer: shoppingListReducer,         \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435         environment: ShoppingListEnviroment(             \/\/ \u0438\u043d\u0436\u0435\u043a\u0442\u0438\u043c \u0441\u0435\u0440\u0432\u0438\u0441 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043c\u043e\u043a\u043e\u0432\u043e\u0433\u043e UUID             uuidGenerator: { UUID(uuidString: &quot;00000000-0000-0000-0000-000000000000&quot;)! }         )     )     store.assert(         \/\/ \u0418\u043c\u0438\u0442\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u0436\u0430\u0442\u0438\u0435 \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0443 &quot;\u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0434\u0443\u043a\u0442&quot;         .send(.addProduct) { newState in             \/\/ \u041e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u044b             newState.products = [                 Product(                     \/\/ \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0443 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0441\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0439 \u0432 \u0441\u0435\u0440\u0432\u0438\u0441\u0435 UUID                     id: UUID(uuidString: &quot;00000000-0000-0000-0000-000000000000&quot;)!,                     name: &quot;&quot;,                     isInBox: false                 )             ]         }     ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0439 \u0442\u0435\u0441\u0442, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0432\u044b\u043f\u0443\u0441\u043a\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0435\u0449\u0435 \u0434\u0432\u0430 \u0441\u0435\u0440\u0432\u0438\u0441\u0430: <strong>saveProducts<\/strong> \u0438 <strong>loadProducts:<\/strong><\/p>\n<p>  <\/p>\n<pre><code class=\"swift\">struct ShoppingListEnviroment {     var uuidGenerator: () -&gt; UUID     var save: ([Product]) -&gt; Effect&lt;Never, Never&gt;     var load: () -&gt; Effect&lt;[Product], Never&gt; }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u044f, \u0447\u0442\u043e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0435, \u043e\u043d\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044e\u0442 <strong>Effect<\/strong>. Effect \u2014 \u043d\u0435 \u0447\u0442\u043e \u0438\u043d\u043e\u0435 \u043a\u0430\u043a Publisher. \u0411\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0435\u0440\u0438\u0438.<\/p>\n<p>  <\/p>\n<p>\u041f\u0438\u0448\u0435\u043c \u0442\u0435\u0441\u0442:<\/p>\n<p>  <\/p>\n<pre><code class=\"swift\">func testAddProduct() {     \/\/ \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c, \u0447\u0442\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u0442\u043e, \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u043e     var savedProducts: [Product] = []     \/\/ \u0443\u0431\u0435\u0434\u0438\u043c\u0441\u044f, \u0447\u0442\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442 \u0441 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u043c     var numberOfSaves = 0     \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u0441\u0442\u043e\u0440     let store = TestStore(         initialState: ShoppingListState(products: []),         reducer: shoppingListReducer,         environment: ShoppingListEnviroment(             uuidGenerator: { .mock },             \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043c\u0430\u0441\u0441\u0438\u0432 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432             \/\/ \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u044d\u0444\u0444\u0435\u043a\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0449\u0438\u0439 \u0441\u043f\u0438\u0441\u043e\u043a             saveProducts: { products in Effect.fireAndForget { savedProducts = products; numberOfSaves += 1 } },             \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0441\u043f\u0438\u0441\u043a\u0430             \/\/ \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u044d\u0444\u0444\u0435\u043a\u0442 \u0441 \u0437\u0430\u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u0441\u043f\u0438\u0441\u043a\u043e\u043c              loadProducts: { Effect(value: [Product(id: .mock, name: &quot;Milk&quot;, isInBox: false)]) }         )     )     store.assert(         \/\/ \u0438\u043c\u043c\u0438\u0442\u0438\u0440\u0443\u0435\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044f load \u043f\u0440\u0438 \u043f\u043e\u043a\u0430\u0437\u0435 view         .send(.loadProducts),         \/\/ \u0441\u043e\u0431\u044b\u0442\u0438\u0435 load \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u044d\u0444\u0444\u0435\u043a\u0442 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445         \/\/ \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 productsLoaded([Product])         .receive(.productsLoaded([Product(id: .mock, name: &quot;Milk&quot;, isInBox: false)])) {             $0.products = [                 Product(id: .mock, name: &quot;Milk&quot;, isInBox: false)             ]         },         \/\/ \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u043e\u0432\u044b\u0439 \u043f\u0440\u043e\u0434\u0443\u043a\u0442 \u0432 \u0441\u043f\u0438\u0441\u043e\u043a         .send(.addProduct) {             $0.products = [                 Product(id: .mock, name: &quot;&quot;, isInBox: false),                 Product(id: .mock, name: &quot;Milk&quot;, isInBox: false)             ]         },         \/\/ \u043e\u0436\u0438\u0434\u0430\u0435\u043c, \u0447\u0442\u043e \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u043b\u043e \u044d\u0444\u0444\u0435\u043a\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f         .receive(.saveProducts),         \/\/ \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442         .do {             XCTAssertEqual(savedProducts, [                 Product(id: .mock, name: &quot;&quot;, isInBox: false),                 Product(id: .mock, name: &quot;Milk&quot;, isInBox: false)             ])         },         \/\/ \u0437\u0430\u0434\u0430\u0435\u043c \u0438\u043c\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u043c\u0443 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0443         .send(.productAction(0, .updateName(&quot;Banana&quot;))) {             $0.products = [                 Product(id: .mock, name: &quot;Banana&quot;, isInBox: false),                 Product(id: .mock, name: &quot;Milk&quot;, isInBox: false)             ]         },         \/\/ \u0438\u043c\u0438\u0442\u0438\u0440\u0443\u0435\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0432 endEditing textFiled'a          .send(.saveProducts),         \/\/ \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442         .do {             XCTAssertEqual(savedProducts, [                 Product(id: .mock, name: &quot;Banana&quot;, isInBox: false),                 Product(id: .mock, name: &quot;Milk&quot;, isInBox: false)             ])         }     )     \/\/ \u0443\u0431\u0435\u0436\u0434\u0430\u0435\u043c\u0441\u044f, \u0447\u0442\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u043e \u0442\u043e\u043b\u044c\u043a\u043e 2 \u0440\u0430\u0437\u0430     XCTAssertEqual(numberOfSaves, 2) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412 \u044d\u0442\u043e\u043c \u0431\u043b\u043e\u043a\u0435 \u043c\u044b:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 unit \u0442\u0435\u0441\u0442\u043e\u0432 \u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0443;<\/li>\n<li>\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043b\u0438 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f;<\/li>\n<li>\u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u0442\u0435\u0441\u0442, \u0438\u043c\u0438\u0442\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430 \u0432 \u0441\u043f\u0438\u0441\u043e\u043a.<\/li>\n<\/ul>\n<p>  <\/p>\n<h2 id=\"unit-snapshot-testy-na-ui\">Unit-Snapshot \u0442\u0435\u0441\u0442\u044b \u043d\u0430 UI<\/h2>\n<p>  <\/p>\n<p>\u0414\u043b\u044f snapshot \u0442\u0435\u0441\u0442\u043e\u0432, \u0430\u0432\u0442\u043e\u0440\u044b Composable Arhitecture \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/github.com\/pointfreeco\/swift-snapshot-testing\" rel=\"nofollow\">SnapshotTesting<\/a> (\u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u044e\u0431\u043e\u0435 \u0434\u0440\u0443\u0433\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435).<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u044d\u043a\u0440\u0430\u043d\u0430 \u0440\u0430\u0432\u043d\u043e\u0435 \u0447\u0435\u0442\u044b\u0440\u0435\u043c: <\/p>\n<p>  <\/p>\n<ul>\n<li>\u043f\u0443\u0441\u0442\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a;<\/li>\n<li>\u0441\u043f\u0438\u0441\u043e\u043a \u0441 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u043c;<\/li>\n<li>\u0441\u043f\u0438\u0441\u043e\u043a \u0441 \u043e\u0434\u043d\u0438\u043c \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u043c;<\/li>\n<li>\u0441\u043f\u0438\u0441\u043e\u043a \u0441 \u043e\u0434\u043d\u0438\u043c \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u043c.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>Composable Architecture \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u043f\u043e\u0434\u0445\u043e\u0434 data-driven development, \u0447\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0431\u043b\u0435\u0433\u0447\u0430\u0435\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 snapshot-\u0442\u0435\u0441\u0442\u043e\u0432 \u2014 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f UI \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u0435\u043a\u0443\u0449\u0438\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b.<\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"swift\">import XCTest import ComposableArchitecture \/\/ \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 \u0434\u043b\u044f \u0441\u043d\u0435\u043f\u0448\u043e\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f import SnapshotTesting @testable import Composable  class ShoppingListSnapshotTests: XCTestCase {      func testEmptyList() {         \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c view         let listView = ShoppingListView(             \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u0443             store: ShoppingListStore(                 \/\/ \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435                 initialState: ShoppingListState(products: []),                 reducer: Reducer { _, _, _ in .none },                 environment: ShoppingListEnviroment.mock             )         )         assertSnapshot(matching: listView, as: .image)     }      func testNewItem() {         let listView = ShoppingListView(             \/\/ \u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c store \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437              \/\/ \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0432\u0435\u0441\u0442\u0438 \u044d\u043a\u0441\u0442\u0435\u043d\u0448\u0435\u043d Store.mock(state:State)             store: .mock(state: ShoppingListState(                 products: [Product(id: .mock, name: &quot;&quot;, isInBox: false)]             ))         )         assertSnapshot(matching: listView, as: .image)     }      func testSingleItem() {         let listView = ShoppingListView(             store: .mock(state: ShoppingListState(                 products: [Product(id: .mock, name: &quot;Milk&quot;, isInBox: false)]             ))         )         assertSnapshot(matching: listView, as: .image)     }      func testCompleteItem() {         let listView = ShoppingListView(             store: .mock(state: ShoppingListState(                 products: [Product(id: .mock, name: &quot;Milk&quot;, isInBox: true)]             ))         )         assertSnapshot(matching: listView, as: .image)     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0432\u0441\u0435\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043d\u0430\u0431\u043e\u0440 \u044d\u0442\u0430\u043b\u043e\u043d\u043d\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/64\/qt\/rf\/64qtrfzauvgptwwo_hqdlyugkc8.png\">  <\/p>\n<p>\u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0431\u044b\u043b\u043e \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u0435\u0433\u043e \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435. <\/p>\n<p>  <\/p>\n<h1 id=\"debug-mode---vishenka-na-torte\">Debug mode \u2014 \u0432\u0438\u0448\u0435\u043d\u043a\u0430 \u043d\u0430 \u0442\u043e\u0440\u0442\u0435<\/h1>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0440\u0435\u0434\u044c\u044e\u0441\u0435\u0440\u0430 \u0435\u0441\u0442\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 debug:<\/p>\n<p>  <\/p>\n<pre><code class=\"swift\">Reducer { state, action, env in     switch action { ... } }.debug() \/\/ \u0438\u043b\u0438 Reducer { state, action, env in     switch action { ... } }.debugActions()<\/code><\/pre>\n<p>  <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f debug \u043b\u043e\u0433\u0438\u0440\u0443\u0435\u0442 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0440\u0435\u0434\u044c\u044e\u0441\u0435\u0440\u0430, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044f \u043a\u0430\u043a\u043e\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u043e \u0438 \u043a\u0430\u043a \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"swift\">received action:   ShoppingListAction.load   (No state changes)  received action:   ShoppingListAction.setupProducts(     [       Product(         id: 9F047826-B431-4D20-9B80-CC65D6A1101B,         name: &quot;&quot;,         isInBox: false       ),       Product(         id: D9834386-75BC-4B9C-B87B-121FFFDB2F93,         name: &quot;Tesggggg&quot;,         isInBox: false       ),       Product(         id: D4405C13-2BB9-4CD4-A3A2-8289EAC6678C,         name: &quot;&quot;,         isInBox: false       ),     ]   ) \u2007 ShoppingListState( \u2007   products: [ +     Product( +       id: 9F047826-B431-4D20-9B80-CC65D6A1101B, +       name: &quot;&quot;, +       isInBox: false +     ), +     Product( +       id: D9834386-75BC-4B9C-B87B-121FFFDB2F93, +       name: &quot;Tesggggg&quot;, +       isInBox: false +     ), +     Product( +       id: D4405C13-2BB9-4CD4-A3A2-8289EAC6678C, +       name: &quot;&quot;, +       isInBox: false +     ), \u2007   ] \u2007 )<\/code><\/pre>\n<p>  <\/p>\n<p>*\u043f\u043b\u044e\u0441\u043e\u043c \u043e\u0442\u043c\u0435\u0447\u0430\u0435\u0442\u0441\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u044b.<\/p>\n<p>  <\/p>\n<h2 id=\"smotri-v-sleduyuschey-serii\">\u0421\u043c\u043e\u0442\u0440\u0438 \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0435\u0440\u0438\u0438<\/h2>\n<p>  <\/p>\n<p>\u0427\u0430\u0441\u0442\u044c 3 \u2014 \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0438 \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0443 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 <em>(in progress)<\/em><\/p>\n<p>  <\/p>\n<p>\u0427\u0430\u0441\u0442\u044c 4 \u2014 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0438 \u0438\u0434\u0435\u043c \u0432 \u043c\u0430\u0433\u0430\u0437\u0438\u043d <em>(in progress)<\/em><\/p>\n<p>  <\/p>\n<h2 id=\"istochniki\">\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438<\/h2>\n<p>  <\/p>\n<p>\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0427\u0430\u0441\u0442\u044c 2: <a href=\"https:\/\/github.com\/onsissond\/Training\/tree\/project\/ComposableArchitecture_part2\" rel=\"nofollow\">github.com<\/a><\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0440\u0442\u0430\u043b \u0430\u0432\u0442\u043e\u0440\u043e\u0432 \u043f\u043e\u0434\u0445\u043e\u0434\u0430: <a href=\"https:\/\/www.pointfree.co\" rel=\"nofollow\">pointfree.co<\/a> <\/p>\n<p>  <\/p>\n<p>\u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438 Composable Architecture: <a href=\"https:\/\/github.com\/pointfreeco\/swift-composable-architecture\" rel=\"nofollow\">https:\/\/github.com\/pointfreeco\/swift-composable-architecture<\/a><\/p>\n<p>  <\/p>\n<p>\u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438 Snaphsot testing: <a href=\"https:\/\/github.com\/pointfreeco\/swift-snapshot-testing\" rel=\"nofollow\">github.com<\/a><\/p>\n<\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/528178\/\"> https:\/\/habr.com\/ru\/post\/528178\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\">\n<p>\u0421\u0431\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0434\u043b\u0435\u0432\u0430\u0435\u0442 \u0436\u0438\u0437\u043d\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u0443 \u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c. <\/p>\n<p>  <\/p>\n<h1 id=\"v-proshloy-serii\">\u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0435\u0440\u0438\u0438<\/h1>\n<p>  <\/p>\n<p><a href=\"https:\/\/habr.com\/ru\/post\/526782\/\">\u0427\u0430\u0441\u0442\u044c 1 \u2014 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b \u0438 \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 Composable Architecture<\/a><\/p>\n<p>  <\/p>\n<h1 id=\"testiruemyy-kod\">\u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u0439 \u043a\u043e\u0434<\/h1>\n<p>  <\/p>\n<p>\u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c \u0432\u044b\u043f\u0443\u0441\u043a\u0435 \u0431\u044b\u043b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d \u043a\u0430\u0440\u043a\u0430\u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u043a\u0443\u043f\u043e\u043a \u043d\u0430 <strong>Composable Architecture<\/strong>. \u041f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c \u043a\u0430\u043a \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u043d\u0430\u0440\u0430\u0449\u0438\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u2014 \u043f\u043e\u043a\u0440\u044b\u0442\u044c \u043a\u043e\u0434 \u0442\u0435\u0441\u0442\u0430\u043c\u0438. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0434\u0432\u0430 \u0432\u0438\u0434\u0430 \u0442\u0435\u0441\u0442\u043e\u0432: unit \u0442\u0435\u0441\u0442\u044b \u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0438 snapshot \u0442\u0435\u0441\u0442\u044b \u043d\u0430 UI. <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-313195","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/313195","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=313195"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/313195\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=313195"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=313195"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=313195"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}